From 7491175bb6d4e5261da4b514ede39d95c09e9c88 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 1 May 2025 13:20:05 +0600 Subject: [PATCH 01/60] feat: add metadata api service and models --- .vscode/settings.json | 1 + android/app/proguard-rules.pro | 9 + lib/collections/fonts.gen.dart | 3 + lib/models/metadata/album.dart | 22 + lib/models/metadata/artist.dart | 14 + lib/models/metadata/image.dart | 13 + lib/models/metadata/metadata.dart | 14 + lib/models/metadata/metadata.freezed.dart | 1868 +++++++++++++++++ lib/models/metadata/metadata.g.dart | 193 ++ lib/models/metadata/playlist.dart | 17 + lib/models/metadata/search.dart | 14 + lib/models/metadata/track.dart | 17 + lib/models/metadata/user.dart | 15 + lib/services/metadata/metadata.dart | 308 +++ linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 8 + pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 21 files changed, 2528 insertions(+) create mode 100644 lib/models/metadata/album.dart create mode 100644 lib/models/metadata/artist.dart create mode 100644 lib/models/metadata/image.dart create mode 100644 lib/models/metadata/metadata.dart create mode 100644 lib/models/metadata/metadata.freezed.dart create mode 100644 lib/models/metadata/metadata.g.dart create mode 100644 lib/models/metadata/playlist.dart create mode 100644 lib/models/metadata/search.dart create mode 100644 lib/models/metadata/track.dart create mode 100644 lib/models/metadata/user.dart create mode 100644 lib/services/metadata/metadata.dart diff --git a/.vscode/settings.json b/.vscode/settings.json index 8ae9c74f..b848774e 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -5,6 +5,7 @@ "ambiguate", "Amoled", "Buildless", + "configurators", "danceability", "fuzzywuzzy", "gapless", diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 17a13e9d..2142555d 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -1,3 +1,12 @@ +#Flutter Wrapper +-keep class io.flutter.app.** { *; } +-keep class io.flutter.plugin.** { *; } +-keep class io.flutter.util.** { *; } +-keep class io.flutter.view.** { *; } +-keep class io.flutter.** { *; } +-keep class io.flutter.plugins.** { *; } +-keep class de.prosiebensat1digital.** { *; } + -keep class androidx.lifecycle.DefaultLifecycleObserver -keepnames class kotlinx.serialization.** { *; } diff --git a/lib/collections/fonts.gen.dart b/lib/collections/fonts.gen.dart index 033d3a79..c51dfd40 100644 --- a/lib/collections/fonts.gen.dart +++ b/lib/collections/fonts.gen.dart @@ -13,6 +13,9 @@ class FontFamily { /// Font family: BootstrapIcons static const String bootstrapIcons = 'BootstrapIcons'; + /// Font family: Cookie + static const String cookie = 'Cookie'; + /// Font family: RadixIcons static const String radixIcons = 'RadixIcons'; } diff --git a/lib/models/metadata/album.dart b/lib/models/metadata/album.dart new file mode 100644 index 00000000..fba880f6 --- /dev/null +++ b/lib/models/metadata/album.dart @@ -0,0 +1,22 @@ +part of 'metadata.dart'; + +enum SpotubeAlbumType { + album, + single, +} + +@freezed +class SpotubeAlbumObject with _$SpotubeAlbumObject { + factory SpotubeAlbumObject({ + required final String uid, + required final String title, + required final SpotubeArtistObject artist, + @Default([]) final List images, + required final String releaseDate, + required final String externalUrl, + required final SpotubeAlbumType type, + }) = _SpotubeAlbumObject; + + factory SpotubeAlbumObject.fromJson(Map json) => + _$SpotubeAlbumObjectFromJson(json); +} diff --git a/lib/models/metadata/artist.dart b/lib/models/metadata/artist.dart new file mode 100644 index 00000000..2dbb7379 --- /dev/null +++ b/lib/models/metadata/artist.dart @@ -0,0 +1,14 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeArtistObject with _$SpotubeArtistObject { + factory SpotubeArtistObject({ + required final String uid, + required final String name, + @Default([]) final List images, + required final String externalUrl, + }) = _SpotubeArtistObject; + + factory SpotubeArtistObject.fromJson(Map json) => + _$SpotubeArtistObjectFromJson(json); +} diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart new file mode 100644 index 00000000..aa20c385 --- /dev/null +++ b/lib/models/metadata/image.dart @@ -0,0 +1,13 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeImageObject with _$SpotubeImageObject { + factory SpotubeImageObject({ + required final String url, + required final int width, + required final int height, + }) = _SpotubeImageObject; + + factory SpotubeImageObject.fromJson(Map json) => + _$SpotubeImageObjectFromJson(json); +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart new file mode 100644 index 00000000..372aec64 --- /dev/null +++ b/lib/models/metadata/metadata.dart @@ -0,0 +1,14 @@ +library metadata_objects; + +import 'package:freezed_annotation/freezed_annotation.dart'; + +part 'metadata.g.dart'; +part 'metadata.freezed.dart'; + +part 'album.dart'; +part 'artist.dart'; +part 'image.dart'; +part 'playlist.dart'; +part 'search.dart'; +part 'track.dart'; +part 'user.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart new file mode 100644 index 00000000..f2364fce --- /dev/null +++ b/lib/models/metadata/metadata.freezed.dart @@ -0,0 +1,1868 @@ +// 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 'metadata.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +SpotubeAlbumObject _$SpotubeAlbumObjectFromJson(Map json) { + return _SpotubeAlbumObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeAlbumObject { + String get uid => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + SpotubeArtistObject get artist => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + String get releaseDate => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + SpotubeAlbumType get type => throw _privateConstructorUsedError; + + /// Serializes this SpotubeAlbumObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeAlbumObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeAlbumObjectCopyWith<$Res> { + factory $SpotubeAlbumObjectCopyWith( + SpotubeAlbumObject value, $Res Function(SpotubeAlbumObject) then) = + _$SpotubeAlbumObjectCopyWithImpl<$Res, SpotubeAlbumObject>; + @useResult + $Res call( + {String uid, + String title, + SpotubeArtistObject artist, + List images, + String releaseDate, + String externalUrl, + SpotubeAlbumType type}); + + $SpotubeArtistObjectCopyWith<$Res> get artist; +} + +/// @nodoc +class _$SpotubeAlbumObjectCopyWithImpl<$Res, $Val extends SpotubeAlbumObject> + implements $SpotubeAlbumObjectCopyWith<$Res> { + _$SpotubeAlbumObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? title = null, + Object? artist = null, + Object? images = null, + Object? releaseDate = null, + Object? externalUrl = null, + Object? type = null, + }) { + return _then(_value.copyWith( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artist: null == artist + ? _value.artist + : artist // ignore: cast_nullable_to_non_nullable + as SpotubeArtistObject, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, + releaseDate: null == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumType, + ) as $Val); + } + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeArtistObjectCopyWith<$Res> get artist { + return $SpotubeArtistObjectCopyWith<$Res>(_value.artist, (value) { + return _then(_value.copyWith(artist: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SpotubeAlbumObjectImplCopyWith<$Res> + implements $SpotubeAlbumObjectCopyWith<$Res> { + factory _$$SpotubeAlbumObjectImplCopyWith(_$SpotubeAlbumObjectImpl value, + $Res Function(_$SpotubeAlbumObjectImpl) then) = + __$$SpotubeAlbumObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String title, + SpotubeArtistObject artist, + List images, + String releaseDate, + String externalUrl, + SpotubeAlbumType type}); + + @override + $SpotubeArtistObjectCopyWith<$Res> get artist; +} + +/// @nodoc +class __$$SpotubeAlbumObjectImplCopyWithImpl<$Res> + extends _$SpotubeAlbumObjectCopyWithImpl<$Res, _$SpotubeAlbumObjectImpl> + implements _$$SpotubeAlbumObjectImplCopyWith<$Res> { + __$$SpotubeAlbumObjectImplCopyWithImpl(_$SpotubeAlbumObjectImpl _value, + $Res Function(_$SpotubeAlbumObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? title = null, + Object? artist = null, + Object? images = null, + Object? releaseDate = null, + Object? externalUrl = null, + Object? type = null, + }) { + return _then(_$SpotubeAlbumObjectImpl( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artist: null == artist + ? _value.artist + : artist // ignore: cast_nullable_to_non_nullable + as SpotubeArtistObject, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, + releaseDate: null == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumType, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeAlbumObjectImpl implements _SpotubeAlbumObject { + _$SpotubeAlbumObjectImpl( + {required this.uid, + required this.title, + required this.artist, + final List images = const [], + required this.releaseDate, + required this.externalUrl, + required this.type}) + : _images = images; + + factory _$SpotubeAlbumObjectImpl.fromJson(Map json) => + _$$SpotubeAlbumObjectImplFromJson(json); + + @override + final String uid; + @override + final String title; + @override + final SpotubeArtistObject artist; + final List _images; + @override + @JsonKey() + List get images { + if (_images is EqualUnmodifiableListView) return _images; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_images); + } + + @override + final String releaseDate; + @override + final String externalUrl; + @override + final SpotubeAlbumType type; + + @override + String toString() { + return 'SpotubeAlbumObject(uid: $uid, title: $title, artist: $artist, images: $images, releaseDate: $releaseDate, externalUrl: $externalUrl, type: $type)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeAlbumObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.title, title) || other.title == title) && + (identical(other.artist, artist) || other.artist == artist) && + const DeepCollectionEquality().equals(other._images, _images) && + (identical(other.releaseDate, releaseDate) || + other.releaseDate == releaseDate) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl) && + (identical(other.type, type) || other.type == type)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + uid, + title, + artist, + const DeepCollectionEquality().hash(_images), + releaseDate, + externalUrl, + type); + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeAlbumObjectImplCopyWith<_$SpotubeAlbumObjectImpl> get copyWith => + __$$SpotubeAlbumObjectImplCopyWithImpl<_$SpotubeAlbumObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeAlbumObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeAlbumObject implements SpotubeAlbumObject { + factory _SpotubeAlbumObject( + {required final String uid, + required final String title, + required final SpotubeArtistObject artist, + final List images, + required final String releaseDate, + required final String externalUrl, + required final SpotubeAlbumType type}) = _$SpotubeAlbumObjectImpl; + + factory _SpotubeAlbumObject.fromJson(Map json) = + _$SpotubeAlbumObjectImpl.fromJson; + + @override + String get uid; + @override + String get title; + @override + SpotubeArtistObject get artist; + @override + List get images; + @override + String get releaseDate; + @override + String get externalUrl; + @override + SpotubeAlbumType get type; + + /// Create a copy of SpotubeAlbumObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeAlbumObjectImplCopyWith<_$SpotubeAlbumObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SpotubeArtistObject _$SpotubeArtistObjectFromJson(Map json) { + return _SpotubeArtistObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeArtistObject { + String get uid => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + + /// Serializes this SpotubeArtistObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeArtistObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeArtistObjectCopyWith<$Res> { + factory $SpotubeArtistObjectCopyWith( + SpotubeArtistObject value, $Res Function(SpotubeArtistObject) then) = + _$SpotubeArtistObjectCopyWithImpl<$Res, SpotubeArtistObject>; + @useResult + $Res call( + {String uid, + String name, + List images, + String externalUrl}); +} + +/// @nodoc +class _$SpotubeArtistObjectCopyWithImpl<$Res, $Val extends SpotubeArtistObject> + implements $SpotubeArtistObjectCopyWith<$Res> { + _$SpotubeArtistObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeArtistObject + /// 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? images = null, + Object? externalUrl = 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, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeArtistObjectImplCopyWith<$Res> + implements $SpotubeArtistObjectCopyWith<$Res> { + factory _$$SpotubeArtistObjectImplCopyWith(_$SpotubeArtistObjectImpl value, + $Res Function(_$SpotubeArtistObjectImpl) then) = + __$$SpotubeArtistObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String name, + List images, + String externalUrl}); +} + +/// @nodoc +class __$$SpotubeArtistObjectImplCopyWithImpl<$Res> + extends _$SpotubeArtistObjectCopyWithImpl<$Res, _$SpotubeArtistObjectImpl> + implements _$$SpotubeArtistObjectImplCopyWith<$Res> { + __$$SpotubeArtistObjectImplCopyWithImpl(_$SpotubeArtistObjectImpl _value, + $Res Function(_$SpotubeArtistObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeArtistObject + /// 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? images = null, + Object? externalUrl = null, + }) { + return _then(_$SpotubeArtistObjectImpl( + 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, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeArtistObjectImpl implements _SpotubeArtistObject { + _$SpotubeArtistObjectImpl( + {required this.uid, + required this.name, + final List images = const [], + required this.externalUrl}) + : _images = images; + + factory _$SpotubeArtistObjectImpl.fromJson(Map json) => + _$$SpotubeArtistObjectImplFromJson(json); + + @override + final String uid; + @override + final String name; + final List _images; + @override + @JsonKey() + List get images { + if (_images is EqualUnmodifiableListView) return _images; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_images); + } + + @override + final String externalUrl; + + @override + String toString() { + return 'SpotubeArtistObject(uid: $uid, name: $name, images: $images, externalUrl: $externalUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeArtistObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._images, _images) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, uid, name, + const DeepCollectionEquality().hash(_images), externalUrl); + + /// Create a copy of SpotubeArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeArtistObjectImplCopyWith<_$SpotubeArtistObjectImpl> get copyWith => + __$$SpotubeArtistObjectImplCopyWithImpl<_$SpotubeArtistObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeArtistObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeArtistObject implements SpotubeArtistObject { + factory _SpotubeArtistObject( + {required final String uid, + required final String name, + final List images, + required final String externalUrl}) = _$SpotubeArtistObjectImpl; + + factory _SpotubeArtistObject.fromJson(Map json) = + _$SpotubeArtistObjectImpl.fromJson; + + @override + String get uid; + @override + String get name; + @override + List get images; + @override + String get externalUrl; + + /// Create a copy of SpotubeArtistObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeArtistObjectImplCopyWith<_$SpotubeArtistObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SpotubeImageObject _$SpotubeImageObjectFromJson(Map json) { + return _SpotubeImageObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeImageObject { + String get url => throw _privateConstructorUsedError; + int get width => throw _privateConstructorUsedError; + int get height => throw _privateConstructorUsedError; + + /// Serializes this SpotubeImageObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeImageObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeImageObjectCopyWith<$Res> { + factory $SpotubeImageObjectCopyWith( + SpotubeImageObject value, $Res Function(SpotubeImageObject) then) = + _$SpotubeImageObjectCopyWithImpl<$Res, SpotubeImageObject>; + @useResult + $Res call({String url, int width, int height}); +} + +/// @nodoc +class _$SpotubeImageObjectCopyWithImpl<$Res, $Val extends SpotubeImageObject> + implements $SpotubeImageObjectCopyWith<$Res> { + _$SpotubeImageObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? width = null, + Object? height = null, + }) { + return _then(_value.copyWith( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + width: null == width + ? _value.width + : width // ignore: cast_nullable_to_non_nullable + as int, + height: null == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeImageObjectImplCopyWith<$Res> + implements $SpotubeImageObjectCopyWith<$Res> { + factory _$$SpotubeImageObjectImplCopyWith(_$SpotubeImageObjectImpl value, + $Res Function(_$SpotubeImageObjectImpl) then) = + __$$SpotubeImageObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String url, int width, int height}); +} + +/// @nodoc +class __$$SpotubeImageObjectImplCopyWithImpl<$Res> + extends _$SpotubeImageObjectCopyWithImpl<$Res, _$SpotubeImageObjectImpl> + implements _$$SpotubeImageObjectImplCopyWith<$Res> { + __$$SpotubeImageObjectImplCopyWithImpl(_$SpotubeImageObjectImpl _value, + $Res Function(_$SpotubeImageObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? width = null, + Object? height = null, + }) { + return _then(_$SpotubeImageObjectImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + width: null == width + ? _value.width + : width // ignore: cast_nullable_to_non_nullable + as int, + height: null == height + ? _value.height + : height // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeImageObjectImpl implements _SpotubeImageObject { + _$SpotubeImageObjectImpl( + {required this.url, required this.width, required this.height}); + + factory _$SpotubeImageObjectImpl.fromJson(Map json) => + _$$SpotubeImageObjectImplFromJson(json); + + @override + final String url; + @override + final int width; + @override + final int height; + + @override + String toString() { + return 'SpotubeImageObject(url: $url, width: $width, height: $height)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeImageObjectImpl && + (identical(other.url, url) || other.url == url) && + (identical(other.width, width) || other.width == width) && + (identical(other.height, height) || other.height == height)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, url, width, height); + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => + __$$SpotubeImageObjectImplCopyWithImpl<_$SpotubeImageObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeImageObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeImageObject implements SpotubeImageObject { + factory _SpotubeImageObject( + {required final String url, + required final int width, + required final int height}) = _$SpotubeImageObjectImpl; + + factory _SpotubeImageObject.fromJson(Map json) = + _$SpotubeImageObjectImpl.fromJson; + + @override + String get url; + @override + int get width; + @override + int get height; + + /// Create a copy of SpotubeImageObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeImageObjectImplCopyWith<_$SpotubeImageObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SpotubePlaylistObject _$SpotubePlaylistObjectFromJson( + Map json) { + return _SpotubePlaylistObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubePlaylistObject { + String get uid => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + SpotubeUserObject get owner => throw _privateConstructorUsedError; + List get collaborators => + throw _privateConstructorUsedError; + + /// Serializes this SpotubePlaylistObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubePlaylistObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubePlaylistObjectCopyWith<$Res> { + factory $SpotubePlaylistObjectCopyWith(SpotubePlaylistObject value, + $Res Function(SpotubePlaylistObject) then) = + _$SpotubePlaylistObjectCopyWithImpl<$Res, SpotubePlaylistObject>; + @useResult + $Res call( + {String uid, + String name, + List images, + String description, + String externalUrl, + SpotubeUserObject owner, + List collaborators}); + + $SpotubeUserObjectCopyWith<$Res> get owner; +} + +/// @nodoc +class _$SpotubePlaylistObjectCopyWithImpl<$Res, + $Val extends SpotubePlaylistObject> + implements $SpotubePlaylistObjectCopyWith<$Res> { + _$SpotubePlaylistObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubePlaylistObject + /// 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? images = null, + Object? description = null, + Object? externalUrl = null, + Object? owner = null, + Object? collaborators = 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, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as SpotubeUserObject, + collaborators: null == collaborators + ? _value.collaborators + : collaborators // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } + + /// Create a copy of SpotubePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeUserObjectCopyWith<$Res> get owner { + return $SpotubeUserObjectCopyWith<$Res>(_value.owner, (value) { + return _then(_value.copyWith(owner: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SpotubePlaylistObjectImplCopyWith<$Res> + implements $SpotubePlaylistObjectCopyWith<$Res> { + factory _$$SpotubePlaylistObjectImplCopyWith( + _$SpotubePlaylistObjectImpl value, + $Res Function(_$SpotubePlaylistObjectImpl) then) = + __$$SpotubePlaylistObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String name, + List images, + String description, + String externalUrl, + SpotubeUserObject owner, + List collaborators}); + + @override + $SpotubeUserObjectCopyWith<$Res> get owner; +} + +/// @nodoc +class __$$SpotubePlaylistObjectImplCopyWithImpl<$Res> + extends _$SpotubePlaylistObjectCopyWithImpl<$Res, + _$SpotubePlaylistObjectImpl> + implements _$$SpotubePlaylistObjectImplCopyWith<$Res> { + __$$SpotubePlaylistObjectImplCopyWithImpl(_$SpotubePlaylistObjectImpl _value, + $Res Function(_$SpotubePlaylistObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubePlaylistObject + /// 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? images = null, + Object? description = null, + Object? externalUrl = null, + Object? owner = null, + Object? collaborators = null, + }) { + return _then(_$SpotubePlaylistObjectImpl( + 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, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as SpotubeUserObject, + collaborators: null == collaborators + ? _value._collaborators + : collaborators // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubePlaylistObjectImpl implements _SpotubePlaylistObject { + _$SpotubePlaylistObjectImpl( + {required this.uid, + required this.name, + final List images = const [], + required this.description, + required this.externalUrl, + required this.owner, + final List collaborators = const []}) + : _images = images, + _collaborators = collaborators; + + factory _$SpotubePlaylistObjectImpl.fromJson(Map json) => + _$$SpotubePlaylistObjectImplFromJson(json); + + @override + final String uid; + @override + final String name; + final List _images; + @override + @JsonKey() + List get images { + if (_images is EqualUnmodifiableListView) return _images; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_images); + } + + @override + final String description; + @override + final String externalUrl; + @override + final SpotubeUserObject owner; + final List _collaborators; + @override + @JsonKey() + List get collaborators { + if (_collaborators is EqualUnmodifiableListView) return _collaborators; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_collaborators); + } + + @override + String toString() { + return 'SpotubePlaylistObject(uid: $uid, name: $name, images: $images, description: $description, externalUrl: $externalUrl, owner: $owner, collaborators: $collaborators)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubePlaylistObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._images, _images) && + (identical(other.description, description) || + other.description == description) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl) && + (identical(other.owner, owner) || other.owner == owner) && + const DeepCollectionEquality() + .equals(other._collaborators, _collaborators)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + uid, + name, + const DeepCollectionEquality().hash(_images), + description, + externalUrl, + owner, + const DeepCollectionEquality().hash(_collaborators)); + + /// Create a copy of SpotubePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubePlaylistObjectImplCopyWith<_$SpotubePlaylistObjectImpl> + get copyWith => __$$SpotubePlaylistObjectImplCopyWithImpl< + _$SpotubePlaylistObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubePlaylistObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubePlaylistObject implements SpotubePlaylistObject { + factory _SpotubePlaylistObject( + {required final String uid, + required final String name, + final List images, + required final String description, + required final String externalUrl, + required final SpotubeUserObject owner, + final List collaborators}) = + _$SpotubePlaylistObjectImpl; + + factory _SpotubePlaylistObject.fromJson(Map json) = + _$SpotubePlaylistObjectImpl.fromJson; + + @override + String get uid; + @override + String get name; + @override + List get images; + @override + String get description; + @override + String get externalUrl; + @override + SpotubeUserObject get owner; + @override + List get collaborators; + + /// Create a copy of SpotubePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubePlaylistObjectImplCopyWith<_$SpotubePlaylistObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeSearchResponseObject _$SpotubeSearchResponseObjectFromJson( + Map json) { + return _SpotubeSearchResponseObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeSearchResponseObject { + List get tracks => throw _privateConstructorUsedError; + List get albums => throw _privateConstructorUsedError; + List get artists => throw _privateConstructorUsedError; + List get playlists => + throw _privateConstructorUsedError; + + /// Serializes this SpotubeSearchResponseObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeSearchResponseObjectCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeSearchResponseObjectCopyWith<$Res> { + factory $SpotubeSearchResponseObjectCopyWith( + SpotubeSearchResponseObject value, + $Res Function(SpotubeSearchResponseObject) then) = + _$SpotubeSearchResponseObjectCopyWithImpl<$Res, + SpotubeSearchResponseObject>; + @useResult + $Res call( + {List tracks, + List albums, + List artists, + List playlists}); +} + +/// @nodoc +class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, + $Val extends SpotubeSearchResponseObject> + implements $SpotubeSearchResponseObjectCopyWith<$Res> { + _$SpotubeSearchResponseObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? tracks = null, + Object? albums = null, + Object? artists = null, + Object? playlists = null, + }) { + return _then(_value.copyWith( + tracks: null == tracks + ? _value.tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, + albums: null == albums + ? _value.albums + : albums // ignore: cast_nullable_to_non_nullable + as List, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + playlists: null == playlists + ? _value.playlists + : playlists // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res> + implements $SpotubeSearchResponseObjectCopyWith<$Res> { + factory _$$SpotubeSearchResponseObjectImplCopyWith( + _$SpotubeSearchResponseObjectImpl value, + $Res Function(_$SpotubeSearchResponseObjectImpl) then) = + __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {List tracks, + List albums, + List artists, + List playlists}); +} + +/// @nodoc +class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> + extends _$SpotubeSearchResponseObjectCopyWithImpl<$Res, + _$SpotubeSearchResponseObjectImpl> + implements _$$SpotubeSearchResponseObjectImplCopyWith<$Res> { + __$$SpotubeSearchResponseObjectImplCopyWithImpl( + _$SpotubeSearchResponseObjectImpl _value, + $Res Function(_$SpotubeSearchResponseObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? tracks = null, + Object? albums = null, + Object? artists = null, + Object? playlists = null, + }) { + return _then(_$SpotubeSearchResponseObjectImpl( + tracks: null == tracks + ? _value._tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, + albums: null == albums + ? _value._albums + : albums // ignore: cast_nullable_to_non_nullable + as List, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + playlists: null == playlists + ? _value._playlists + : playlists // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +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; + + 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; + @override + @JsonKey() + List get albums { + if (_albums is EqualUnmodifiableListView) return _albums; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_albums); + } + + final List _artists; + @override + @JsonKey() + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + final List _playlists; + @override + @JsonKey() + List get playlists { + if (_playlists is EqualUnmodifiableListView) return _playlists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_playlists); + } + + @override + String toString() { + return 'SpotubeSearchResponseObject(tracks: $tracks, albums: $albums, artists: $artists, playlists: $playlists)'; + } + + @override + bool operator ==(Object other) { + 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)); + } + + @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)); + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> + get copyWith => __$$SpotubeSearchResponseObjectImplCopyWithImpl< + _$SpotubeSearchResponseObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeSearchResponseObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeSearchResponseObject + implements SpotubeSearchResponseObject { + factory _SpotubeSearchResponseObject( + {final List tracks, + final List albums, + final List artists, + final List playlists}) = + _$SpotubeSearchResponseObjectImpl; + + factory _SpotubeSearchResponseObject.fromJson(Map json) = + _$SpotubeSearchResponseObjectImpl.fromJson; + + @override + List get tracks; + @override + List get albums; + @override + List get artists; + @override + List get playlists; + + /// Create a copy of SpotubeSearchResponseObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeSearchResponseObjectImplCopyWith<_$SpotubeSearchResponseObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map json) { + return _SpotubeTrackObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeTrackObject { + String get uid => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + List get artists => throw _privateConstructorUsedError; + SpotubeAlbumObject get album => throw _privateConstructorUsedError; + int get durationMs => throw _privateConstructorUsedError; + String get isrc => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + + /// Serializes this SpotubeTrackObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeTrackObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeTrackObjectCopyWith<$Res> { + factory $SpotubeTrackObjectCopyWith( + SpotubeTrackObject value, $Res Function(SpotubeTrackObject) then) = + _$SpotubeTrackObjectCopyWithImpl<$Res, SpotubeTrackObject>; + @useResult + $Res call( + {String uid, + String title, + List artists, + SpotubeAlbumObject album, + int durationMs, + String isrc, + String externalUrl}); + + $SpotubeAlbumObjectCopyWith<$Res> get album; +} + +/// @nodoc +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 SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? title = null, + Object? artists = null, + Object? album = null, + Object? durationMs = null, + Object? isrc = null, + Object? externalUrl = null, + }) { + return _then(_value.copyWith( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + album: null == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumObject, + 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, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeAlbumObjectCopyWith<$Res> get album { + return $SpotubeAlbumObjectCopyWith<$Res>(_value.album, (value) { + return _then(_value.copyWith(album: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SpotubeTrackObjectImplCopyWith<$Res> + implements $SpotubeTrackObjectCopyWith<$Res> { + factory _$$SpotubeTrackObjectImplCopyWith(_$SpotubeTrackObjectImpl value, + $Res Function(_$SpotubeTrackObjectImpl) then) = + __$$SpotubeTrackObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String title, + List artists, + SpotubeAlbumObject album, + int durationMs, + String isrc, + String externalUrl}); + + @override + $SpotubeAlbumObjectCopyWith<$Res> get album; +} + +/// @nodoc +class __$$SpotubeTrackObjectImplCopyWithImpl<$Res> + extends _$SpotubeTrackObjectCopyWithImpl<$Res, _$SpotubeTrackObjectImpl> + implements _$$SpotubeTrackObjectImplCopyWith<$Res> { + __$$SpotubeTrackObjectImplCopyWithImpl(_$SpotubeTrackObjectImpl _value, + $Res Function(_$SpotubeTrackObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? uid = null, + Object? title = null, + Object? artists = null, + Object? album = null, + Object? durationMs = null, + Object? isrc = null, + Object? externalUrl = null, + }) { + return _then(_$SpotubeTrackObjectImpl( + uid: null == uid + ? _value.uid + : uid // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + album: null == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumObject, + 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, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeTrackObjectImpl implements _SpotubeTrackObject { + _$SpotubeTrackObjectImpl( + {required this.uid, + required this.title, + final List artists = const [], + required this.album, + required this.durationMs, + required this.isrc, + required this.externalUrl}) + : _artists = artists; + + factory _$SpotubeTrackObjectImpl.fromJson(Map json) => + _$$SpotubeTrackObjectImplFromJson(json); + + @override + final String uid; + @override + final String title; + final List _artists; + @override + @JsonKey() + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + @override + final SpotubeAlbumObject album; + @override + final int durationMs; + @override + final String isrc; + @override + final String externalUrl; + + @override + String toString() { + return 'SpotubeTrackObject(uid: $uid, title: $title, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, externalUrl: $externalUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeTrackObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.title, title) || other.title == title) && + const DeepCollectionEquality().equals(other._artists, _artists) && + (identical(other.album, album) || other.album == album) && + (identical(other.durationMs, durationMs) || + other.durationMs == durationMs) && + (identical(other.isrc, isrc) || other.isrc == isrc) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + uid, + title, + const DeepCollectionEquality().hash(_artists), + album, + durationMs, + isrc, + externalUrl); + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeTrackObjectImplCopyWith<_$SpotubeTrackObjectImpl> get copyWith => + __$$SpotubeTrackObjectImplCopyWithImpl<_$SpotubeTrackObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeTrackObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeTrackObject implements SpotubeTrackObject { + factory _SpotubeTrackObject( + {required final String uid, + required final String title, + final List artists, + required final SpotubeAlbumObject album, + required final int durationMs, + required final String isrc, + required final String externalUrl}) = _$SpotubeTrackObjectImpl; + + factory _SpotubeTrackObject.fromJson(Map json) = + _$SpotubeTrackObjectImpl.fromJson; + + @override + String get uid; + @override + String get title; + @override + List get artists; + @override + SpotubeAlbumObject get album; + @override + int get durationMs; + @override + String get isrc; + @override + String get externalUrl; + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeTrackObjectImplCopyWith<_$SpotubeTrackObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} + +SpotubeUserObject _$SpotubeUserObjectFromJson(Map json) { + return _SpotubeUserObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeUserObject { + String get uid => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get avatars => throw _privateConstructorUsedError; + String get externalUrl => throw _privateConstructorUsedError; + String get displayName => throw _privateConstructorUsedError; + + /// Serializes this SpotubeUserObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeUserObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeUserObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeUserObjectCopyWith<$Res> { + factory $SpotubeUserObjectCopyWith( + SpotubeUserObject value, $Res Function(SpotubeUserObject) then) = + _$SpotubeUserObjectCopyWithImpl<$Res, SpotubeUserObject>; + @useResult + $Res call( + {String uid, + String name, + List avatars, + String externalUrl, + String displayName}); +} + +/// @nodoc +class _$SpotubeUserObjectCopyWithImpl<$Res, $Val extends SpotubeUserObject> + implements $SpotubeUserObjectCopyWith<$Res> { + _$SpotubeUserObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeUserObject + /// 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? avatars = null, + Object? externalUrl = null, + Object? displayName = 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, + avatars: null == avatars + ? _value.avatars + : avatars // ignore: cast_nullable_to_non_nullable + as List, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + displayName: null == displayName + ? _value.displayName + : displayName // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeUserObjectImplCopyWith<$Res> + implements $SpotubeUserObjectCopyWith<$Res> { + factory _$$SpotubeUserObjectImplCopyWith(_$SpotubeUserObjectImpl value, + $Res Function(_$SpotubeUserObjectImpl) then) = + __$$SpotubeUserObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String uid, + String name, + List avatars, + String externalUrl, + String displayName}); +} + +/// @nodoc +class __$$SpotubeUserObjectImplCopyWithImpl<$Res> + extends _$SpotubeUserObjectCopyWithImpl<$Res, _$SpotubeUserObjectImpl> + implements _$$SpotubeUserObjectImplCopyWith<$Res> { + __$$SpotubeUserObjectImplCopyWithImpl(_$SpotubeUserObjectImpl _value, + $Res Function(_$SpotubeUserObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeUserObject + /// 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? avatars = null, + Object? externalUrl = null, + Object? displayName = null, + }) { + return _then(_$SpotubeUserObjectImpl( + 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, + avatars: null == avatars + ? _value._avatars + : avatars // ignore: cast_nullable_to_non_nullable + as List, + externalUrl: null == externalUrl + ? _value.externalUrl + : externalUrl // ignore: cast_nullable_to_non_nullable + as String, + displayName: null == displayName + ? _value.displayName + : displayName // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeUserObjectImpl implements _SpotubeUserObject { + _$SpotubeUserObjectImpl( + {required this.uid, + required this.name, + final List avatars = const [], + required this.externalUrl, + required this.displayName}) + : _avatars = avatars; + + factory _$SpotubeUserObjectImpl.fromJson(Map json) => + _$$SpotubeUserObjectImplFromJson(json); + + @override + final String uid; + @override + final String name; + final List _avatars; + @override + @JsonKey() + List get avatars { + if (_avatars is EqualUnmodifiableListView) return _avatars; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_avatars); + } + + @override + final String externalUrl; + @override + final String displayName; + + @override + String toString() { + return 'SpotubeUserObject(uid: $uid, name: $name, avatars: $avatars, externalUrl: $externalUrl, displayName: $displayName)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeUserObjectImpl && + (identical(other.uid, uid) || other.uid == uid) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._avatars, _avatars) && + (identical(other.externalUrl, externalUrl) || + other.externalUrl == externalUrl) && + (identical(other.displayName, displayName) || + other.displayName == displayName)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, uid, name, + const DeepCollectionEquality().hash(_avatars), externalUrl, displayName); + + /// Create a copy of SpotubeUserObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => + __$$SpotubeUserObjectImplCopyWithImpl<_$SpotubeUserObjectImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$SpotubeUserObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeUserObject implements SpotubeUserObject { + factory _SpotubeUserObject( + {required final String uid, + required final String name, + final List avatars, + required final String externalUrl, + required final String displayName}) = _$SpotubeUserObjectImpl; + + factory _SpotubeUserObject.fromJson(Map json) = + _$SpotubeUserObjectImpl.fromJson; + + @override + String get uid; + @override + String get name; + @override + List get avatars; + @override + String get externalUrl; + @override + String get displayName; + + /// Create a copy of SpotubeUserObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart new file mode 100644 index 00000000..ee386b19 --- /dev/null +++ b/lib/models/metadata/metadata.g.dart @@ -0,0 +1,193 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'metadata.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$SpotubeAlbumObjectImpl _$$SpotubeAlbumObjectImplFromJson(Map json) => + _$SpotubeAlbumObjectImpl( + uid: json['uid'] as String, + title: json['title'] as String, + artist: SpotubeArtistObject.fromJson( + Map.from(json['artist'] as Map)), + images: (json['images'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + releaseDate: json['releaseDate'] as String, + externalUrl: json['externalUrl'] as String, + type: $enumDecode(_$SpotubeAlbumTypeEnumMap, json['type']), + ); + +Map _$$SpotubeAlbumObjectImplToJson( + _$SpotubeAlbumObjectImpl instance) => + { + 'uid': instance.uid, + 'title': instance.title, + 'artist': instance.artist.toJson(), + 'images': instance.images.map((e) => e.toJson()).toList(), + 'releaseDate': instance.releaseDate, + 'externalUrl': instance.externalUrl, + 'type': _$SpotubeAlbumTypeEnumMap[instance.type]!, + }; + +const _$SpotubeAlbumTypeEnumMap = { + SpotubeAlbumType.album: 'album', + SpotubeAlbumType.single: 'single', +}; + +_$SpotubeArtistObjectImpl _$$SpotubeArtistObjectImplFromJson(Map json) => + _$SpotubeArtistObjectImpl( + uid: json['uid'] as String, + name: json['name'] as String, + images: (json['images'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + externalUrl: json['externalUrl'] as String, + ); + +Map _$$SpotubeArtistObjectImplToJson( + _$SpotubeArtistObjectImpl instance) => + { + 'uid': instance.uid, + 'name': instance.name, + 'images': instance.images.map((e) => e.toJson()).toList(), + 'externalUrl': instance.externalUrl, + }; + +_$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => + _$SpotubeImageObjectImpl( + url: json['url'] as String, + width: (json['width'] as num).toInt(), + height: (json['height'] as num).toInt(), + ); + +Map _$$SpotubeImageObjectImplToJson( + _$SpotubeImageObjectImpl instance) => + { + 'url': instance.url, + 'width': instance.width, + 'height': instance.height, + }; + +_$SpotubePlaylistObjectImpl _$$SpotubePlaylistObjectImplFromJson(Map json) => + _$SpotubePlaylistObjectImpl( + uid: json['uid'] as String, + name: json['name'] as String, + images: (json['images'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + description: json['description'] as String, + externalUrl: json['externalUrl'] as String, + owner: SpotubeUserObject.fromJson( + Map.from(json['owner'] as Map)), + collaborators: (json['collaborators'] as List?) + ?.map((e) => SpotubeUserObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + ); + +Map _$$SpotubePlaylistObjectImplToJson( + _$SpotubePlaylistObjectImpl instance) => + { + 'uid': instance.uid, + 'name': instance.name, + 'images': instance.images.map((e) => e.toJson()).toList(), + 'description': instance.description, + 'externalUrl': instance.externalUrl, + 'owner': instance.owner.toJson(), + 'collaborators': instance.collaborators.map((e) => e.toJson()).toList(), + }; + +_$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 [], + ); + +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(), + }; + +_$SpotubeTrackObjectImpl _$$SpotubeTrackObjectImplFromJson(Map json) => + _$SpotubeTrackObjectImpl( + uid: json['uid'] as String, + title: json['title'] as String, + artists: (json['artists'] as List?) + ?.map((e) => SpotubeArtistObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + album: SpotubeAlbumObject.fromJson( + Map.from(json['album'] as Map)), + durationMs: (json['durationMs'] as num).toInt(), + isrc: json['isrc'] as String, + externalUrl: json['externalUrl'] as String, + ); + +Map _$$SpotubeTrackObjectImplToJson( + _$SpotubeTrackObjectImpl instance) => + { + 'uid': instance.uid, + 'title': instance.title, + 'artists': instance.artists.map((e) => e.toJson()).toList(), + 'album': instance.album.toJson(), + 'durationMs': instance.durationMs, + 'isrc': instance.isrc, + 'externalUrl': instance.externalUrl, + }; + +_$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) => + _$SpotubeUserObjectImpl( + uid: json['uid'] as String, + name: json['name'] as String, + avatars: (json['avatars'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + externalUrl: json['externalUrl'] as String, + displayName: json['displayName'] as String, + ); + +Map _$$SpotubeUserObjectImplToJson( + _$SpotubeUserObjectImpl instance) => + { + 'uid': instance.uid, + 'name': instance.name, + 'avatars': instance.avatars.map((e) => e.toJson()).toList(), + 'externalUrl': instance.externalUrl, + 'displayName': instance.displayName, + }; diff --git a/lib/models/metadata/playlist.dart b/lib/models/metadata/playlist.dart new file mode 100644 index 00000000..d6c686dd --- /dev/null +++ b/lib/models/metadata/playlist.dart @@ -0,0 +1,17 @@ +part of 'metadata.dart'; + +@freezed +class SpotubePlaylistObject with _$SpotubePlaylistObject { + factory SpotubePlaylistObject({ + required final String uid, + required final String name, + @Default([]) final List images, + required final String description, + required final String externalUrl, + required final SpotubeUserObject owner, + @Default([]) final List collaborators, + }) = _SpotubePlaylistObject; + + factory SpotubePlaylistObject.fromJson(Map json) => + _$SpotubePlaylistObjectFromJson(json); +} diff --git a/lib/models/metadata/search.dart b/lib/models/metadata/search.dart new file mode 100644 index 00000000..011238f7 --- /dev/null +++ b/lib/models/metadata/search.dart @@ -0,0 +1,14 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject { + factory SpotubeSearchResponseObject({ + @Default([]) final List tracks, + @Default([]) final List albums, + @Default([]) final List artists, + @Default([]) final List playlists, + }) = _SpotubeSearchResponseObject; + + factory SpotubeSearchResponseObject.fromJson(Map json) => + _$SpotubeSearchResponseObjectFromJson(json); +} diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart new file mode 100644 index 00000000..f555b27f --- /dev/null +++ b/lib/models/metadata/track.dart @@ -0,0 +1,17 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeTrackObject with _$SpotubeTrackObject { + factory SpotubeTrackObject({ + required final String uid, + required final String title, + @Default([]) final List artists, + required final SpotubeAlbumObject album, + required final int durationMs, + required final String isrc, + required final String externalUrl, + }) = _SpotubeTrackObject; + + factory SpotubeTrackObject.fromJson(Map json) => + _$SpotubeTrackObjectFromJson(json); +} diff --git a/lib/models/metadata/user.dart b/lib/models/metadata/user.dart new file mode 100644 index 00000000..878f2da6 --- /dev/null +++ b/lib/models/metadata/user.dart @@ -0,0 +1,15 @@ +part of 'metadata.dart'; + +@freezed +class SpotubeUserObject with _$SpotubeUserObject { + factory SpotubeUserObject({ + required final String uid, + required final String name, + @Default([]) final List avatars, + required final String externalUrl, + required final String displayName, + }) = _SpotubeUserObject; + + factory SpotubeUserObject.fromJson(Map json) => + _$SpotubeUserObjectFromJson(json); +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart new file mode 100644 index 00000000..29df4a7d --- /dev/null +++ b/lib/services/metadata/metadata.dart @@ -0,0 +1,308 @@ +import 'dart:async'; +import 'dart:convert'; + +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; + +/// Signature for metadata and related methods that will return Spotube native +/// objects e.g. SpotubeTrack, SpotubePlaylist, etc. +class MetadataApiSignature { + late final JavascriptRuntime runtime; + + MetadataApiSignature(String libraryCode) { + runtime = getJavascriptRuntime(xhr: true); + runtime.enableHandlePromises(); + + Timer.periodic( + const Duration(milliseconds: 100), + (timer) { + runtime.executePendingJob(); + }, + ); + + runtime.evaluate( + """ + ;$libraryCode; + const metadataApi = new MetadataApi(); + """, + ); + } + + void dispose() { + runtime.dispose(); + } + + Future invoke(String method, [List? args]) async { + final completer = Completer(); + runtime.onMessage(method, (result) { + try { + if (result == null) { + completer.completeError("Result is null"); + } else { + completer.complete(result is String ? jsonDecode(result) : result); + } + } catch (e, stack) { + AppLogger.reportError(e, stack); + } + }); + final code = """ + $method(...${args != null ? jsonEncode(args) : "[]"}) + .then((res) => { + sendMessage("$method", JSON.stringify(res)); + }).catch((err) => { + sendMessage("$method", null); + async}){ + } final res"metadataApi.=>", [limit, offset] ;= await invoke() + + return res.map(es.fromJson).toList(); + """; + + final res = await runtime.evaluateAsync(code); + + if (res.isError) { + AppLogger.reportError("Error evaluating code: $code\n${res.rawResult}"); + completer.completeError("Error evaluating code: $code\n${res.rawResult}"); + return completer.future; + } + + return completer.future; + } + + // ----- Track ------ + Future getTrack(String id) async { + final result = await invoke("metadataApi.getTrack", [id]); + return SpotubeTrackObject.fromJson(result); + } + + Future> listTracks({ + List? ids, + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final result = await invoke( + "metadataApi.listTracks", + [ + ids, + limit, + offset, + ], + ); + + return result.map(SpotubeTrackObject.fromJson).toList(); + } + + Future> listTracksByAlbum( + String albumId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listTracksByAlbum", + [albumId, limit, offset], + ); + + return res.map(SpotubeTrackObject.fromJson).toList(); + } + + Future> listTopTracksByArtist( + String artistId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listTopTracksByArtist", + [artistId, limit, offset], + ); + + return res.map(SpotubeTrackObject.fromJson).toList(); + } + + Future> listTracksByPlaylist( + String playlistId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listTracksByPlaylist", + [playlistId, limit, offset], + ); + + return res.map(SpotubeTrackObject.fromJson).toList(); + } + + Future> listUserSavedTracks( + String userId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listUserSavedTracks", + [userId, limit, offset], + ); + + return res.map(SpotubeTrackObject.fromJson).toList(); + } + + // ----- Album ------ + Future getAlbum(String id) async { + final res = await invoke("metadataApi.getAlbum", [id]); + + return SpotubeAlbumObject.fromJson(res); + } + + Future> listAlbums({ + List? ids, + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listAlbums", + [ids, limit, offset], + ); + + return res.map(SpotubeAlbumObject.fromJson).toList(); + } + + Future> listAlbumsByArtist( + String artistId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listAlbumsByArtist", + [artistId, limit, offset], + ); + + return res.map(SpotubeAlbumObject.fromJson).toList(); + } + + Future> listUserSavedAlbums( + String userId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listUserSavedAlbums", + [userId, limit, offset], + ); + + return res.map(SpotubeAlbumObject.fromJson).toList(); + } + + // ----- Playlist ------ + Future getPlaylist(String id) async { + final res = await invoke("metadataApi.getPlaylist", [id]); + + return SpotubePlaylistObject.fromJson(res); + } + + Future> listPlaylists({ + List? ids, + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listPlaylists", + [ids, limit, offset], + ); + + return res.map(SpotubePlaylistObject.fromJson).toList(); + } + + Future> listUserSavedPlaylists( + String userId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listUserSavedPlaylists", + [userId, limit, offset], + ); + + return res.map(SpotubePlaylistObject.fromJson).toList(); + } + + // ----- Artist ------ + Future getArtist(String id) async { + final res = await invoke("metadataApi.getArtist", [id]); + + return SpotubeArtistObject.fromJson(res); + } + + Future> listArtists({ + List? ids, + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listArtists", + [ids, limit, offset], + ); + + return res.map(SpotubeArtistObject.fromJson).toList(); + } + + Future> listUserSavedArtists( + String userId, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.listUserSavedArtists", + [userId, limit, offset], + ); + + return res.map(SpotubeArtistObject.fromJson).toList(); + } + + // ----- Search ------ + Future search( + String query, { + int limit = defaultMetadataLimit, + int offset = defaultMetadataOffset, + }) async { + final res = await invoke( + "metadataApi.search", + [query, limit, offset], + ); + + return res.map(SpotubeSearchResponseObject.fromJson).toList(); + } + + // ----- User ------ + Future followArtist(String userId, String artistId) async { + await invoke("metadataApi.followArtist", [userId, artistId]); + } + + Future unfollowArtist(String userId, String artistId) async { + await invoke("metadataApi.unfollowArtist", [userId, artistId]); + } + + Future savePlaylist(String userId, String playlistId) async { + await invoke("metadataApi.savePlaylist", [userId, playlistId]); + } + + Future unsavePlaylist(String userId, String playlistId) async { + await invoke("metadataApi.unsavePlaylist", [userId, playlistId]); + } + + Future saveAlbum(String userId, String albumId) async { + await invoke("metadataApi.saveAlbum", [userId, albumId]); + } + + Future unsaveAlbum(String userId, String albumId) async { + await invoke("metadataApi.unsaveAlbum", [userId, albumId]); + } + + Future saveTrack(String userId, String trackId) async { + await invoke("metadataApi.saveTrack", [userId, trackId]); + } + + Future unsaveTrack(String userId, String trackId) async { + await invoke("metadataApi.unsaveTrack", [userId, trackId]); + } +} diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 06c33e05..3976fd80 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -27,6 +28,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_js_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterJsPlugin"); + flutter_js_plugin_register_with_registrar(flutter_js_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index e98b18eb..c47d2627 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_linux + flutter_js flutter_secure_storage_linux gtk local_notifier diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index b92d7882..d44e5a13 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,6 +14,7 @@ import desktop_webview_window import device_info_plus import file_selector_macos import flutter_inappwebview_macos +import flutter_js import flutter_secure_storage_macos import local_notifier import media_kit_libs_macos_audio @@ -39,6 +40,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) + FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin")) diff --git a/pubspec.lock b/pubspec.lock index db9cdb80..76e05a60 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -909,6 +909,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" + flutter_js: + dependency: "direct main" + description: + name: flutter_js + sha256: "6b777cd4e468546f046a2f114d078a4596143269f6fa6bad5c29611d5b896369" + url: "https://pub.dev" + source: hosted + version: "0.8.2" flutter_launcher_icons: dependency: "direct dev" description: diff --git a/pubspec.yaml b/pubspec.yaml index f3be4108..be622bf1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,6 +142,7 @@ dependencies: collection: any otp_util: ^1.0.2 dio_http2_adapter: ^2.6.0 + flutter_js: ^0.8.2 dev_dependencies: build_runner: ^2.4.13 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d1bee122..9ad3a3a5 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -36,6 +37,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); + FlutterJsPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterJsPlugin")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); LocalNotifierPluginRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 32c8a634..a9b74bf4 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,6 +9,7 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_windows flutter_inappwebview_windows + flutter_js flutter_secure_storage_windows local_notifier media_kit_libs_windows_audio From 9a861b9954ec25d65c5b81e6e6b2ea2eda51302a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 1 May 2025 23:51:22 +0600 Subject: [PATCH 02/60] chore: add proper error handling --- lib/services/metadata/metadata.dart | 45 ++++++++++++++++++++--------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 29df4a7d..138af397 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -1,6 +1,8 @@ import 'dart:async'; import 'dart:convert'; +import 'package:flutter_js/extensions/fetch.dart'; +import 'package:flutter_js/extensions/xhr.dart'; import 'package:flutter_js/flutter_js.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -11,11 +13,14 @@ const int defaultMetadataOffset = 0; /// Signature for metadata and related methods that will return Spotube native /// objects e.g. SpotubeTrack, SpotubePlaylist, etc. class MetadataApiSignature { - late final JavascriptRuntime runtime; + final JavascriptRuntime runtime; - MetadataApiSignature(String libraryCode) { - runtime = getJavascriptRuntime(xhr: true); + MetadataApiSignature._(this.runtime); + + static Future init(String libraryCode) async { + final runtime = getJavascriptRuntime(xhr: true).enableXhr(); runtime.enableHandlePromises(); + await runtime.enableFetch(); Timer.periodic( const Duration(milliseconds: 100), @@ -24,12 +29,20 @@ class MetadataApiSignature { }, ); - runtime.evaluate( + final res = runtime.evaluate( """ ;$libraryCode; const metadataApi = new MetadataApi(); """, ); + + if (res.isError) { + AppLogger.reportError( + "Error evaluating code: $libraryCode\n${res.rawResult}", + ); + } + + return MetadataApiSignature._(runtime); } void dispose() { @@ -40,8 +53,8 @@ class MetadataApiSignature { final completer = Completer(); runtime.onMessage(method, (result) { try { - if (result == null) { - completer.completeError("Result is null"); + if (result is Map && result.containsKey("error")) { + completer.completeError(result["error"]); } else { completer.complete(result is String ? jsonDecode(result) : result); } @@ -52,13 +65,19 @@ class MetadataApiSignature { final code = """ $method(...${args != null ? jsonEncode(args) : "[]"}) .then((res) => { - sendMessage("$method", JSON.stringify(res)); - }).catch((err) => { - sendMessage("$method", null); - async}){ - } final res"metadataApi.=>", [limit, offset] ;= await invoke() - - return res.map(es.fromJson).toList(); + try { + sendMessage("$method", JSON.stringify(res)); + } catch (e) { + console.error("Failed to send message in $method.then: ", `\${e.toString()}\n\${e.stack.toString()}`); + } + }).catch((e) => { + try { + console.error("Error in $method: ", `\${e.toString()}\n\${e.stack.toString()}`); + sendMessage("$method", JSON.stringify({error: `\${e.toString()}\n\${e.stack.toString()}`})); + } catch (e) { + console.error("Failed to send message in $method.catch: ", `\${e.toString()}\n\${e.stack.toString()}`); + } + }); """; final res = await runtime.evaluateAsync(code); From 8ac30c0031899eb5f172d359ea02ed52faae2dd3 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 2 May 2025 11:36:09 +0600 Subject: [PATCH 03/60] 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 ------ From abe04b28b2194616d11ae362c8697968b358affb Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 2 May 2025 13:52:07 +0600 Subject: [PATCH 04/60] feat(metadata-plugin): add local storage api --- lib/models/metadata/metadata.dart | 2 + lib/models/metadata/metadata.freezed.dart | 238 +++++++++++++++++++ lib/models/metadata/metadata.g.dart | 23 ++ lib/models/metadata/plugin.dart | 21 ++ lib/services/metadata/apis/localstorage.dart | 60 +++++ lib/services/metadata/metadata.dart | 20 +- 6 files changed, 361 insertions(+), 3 deletions(-) create mode 100644 lib/models/metadata/plugin.dart create mode 100644 lib/services/metadata/apis/localstorage.dart diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 0e17d796..99f7c687 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -14,3 +14,5 @@ part 'playlist.dart'; part 'search.dart'; part 'track.dart'; part 'user.dart'; + +part 'plugin.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index c7777b76..b5a2599f 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -2430,3 +2430,241 @@ abstract class _SpotubeUserObject implements SpotubeUserObject { _$$SpotubeUserObjectImplCopyWith<_$SpotubeUserObjectImpl> get copyWith => throw _privateConstructorUsedError; } + +PluginConfiguration _$PluginConfigurationFromJson(Map json) { + return _PluginConfiguration.fromJson(json); +} + +/// @nodoc +mixin _$PluginConfiguration { + PluginType get type => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get version => throw _privateConstructorUsedError; + String get author => throw _privateConstructorUsedError; + + /// Serializes this PluginConfiguration to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $PluginConfigurationCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $PluginConfigurationCopyWith<$Res> { + factory $PluginConfigurationCopyWith( + PluginConfiguration value, $Res Function(PluginConfiguration) then) = + _$PluginConfigurationCopyWithImpl<$Res, PluginConfiguration>; + @useResult + $Res call( + {PluginType type, + String name, + String description, + String version, + String author}); +} + +/// @nodoc +class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration> + implements $PluginConfigurationCopyWith<$Res> { + _$PluginConfigurationCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? name = null, + Object? description = null, + Object? version = null, + Object? author = null, + }) { + return _then(_value.copyWith( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as PluginType, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + author: null == author + ? _value.author + : author // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$PluginConfigurationImplCopyWith<$Res> + implements $PluginConfigurationCopyWith<$Res> { + factory _$$PluginConfigurationImplCopyWith(_$PluginConfigurationImpl value, + $Res Function(_$PluginConfigurationImpl) then) = + __$$PluginConfigurationImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {PluginType type, + String name, + String description, + String version, + String author}); +} + +/// @nodoc +class __$$PluginConfigurationImplCopyWithImpl<$Res> + extends _$PluginConfigurationCopyWithImpl<$Res, _$PluginConfigurationImpl> + implements _$$PluginConfigurationImplCopyWith<$Res> { + __$$PluginConfigurationImplCopyWithImpl(_$PluginConfigurationImpl _value, + $Res Function(_$PluginConfigurationImpl) _then) + : super(_value, _then); + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? type = null, + Object? name = null, + Object? description = null, + Object? version = null, + Object? author = null, + }) { + return _then(_$PluginConfigurationImpl( + type: null == type + ? _value.type + : type // ignore: cast_nullable_to_non_nullable + as PluginType, + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + version: null == version + ? _value.version + : version // ignore: cast_nullable_to_non_nullable + as String, + author: null == author + ? _value.author + : author // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$PluginConfigurationImpl extends _PluginConfiguration { + _$PluginConfigurationImpl( + {required this.type, + required this.name, + required this.description, + required this.version, + required this.author}) + : super._(); + + factory _$PluginConfigurationImpl.fromJson(Map json) => + _$$PluginConfigurationImplFromJson(json); + + @override + final PluginType type; + @override + final String name; + @override + final String description; + @override + final String version; + @override + final String author; + + @override + String toString() { + return 'PluginConfiguration(type: $type, name: $name, description: $description, version: $version, author: $author)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$PluginConfigurationImpl && + (identical(other.type, type) || other.type == type) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.version, version) || other.version == version) && + (identical(other.author, author) || other.author == author)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, type, name, description, version, author); + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => + __$$PluginConfigurationImplCopyWithImpl<_$PluginConfigurationImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$PluginConfigurationImplToJson( + this, + ); + } +} + +abstract class _PluginConfiguration extends PluginConfiguration { + factory _PluginConfiguration( + {required final PluginType type, + required final String name, + required final String description, + required final String version, + required final String author}) = _$PluginConfigurationImpl; + _PluginConfiguration._() : super._(); + + factory _PluginConfiguration.fromJson(Map json) = + _$PluginConfigurationImpl.fromJson; + + @override + PluginType get type; + @override + String get name; + @override + String get description; + @override + String get version; + @override + String get author; + + /// Create a copy of PluginConfiguration + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 5e76a653..96c8119c 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -223,3 +223,26 @@ Map _$$SpotubeUserObjectImplToJson( 'externalUrl': instance.externalUrl, 'displayName': instance.displayName, }; + +_$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) => + _$PluginConfigurationImpl( + type: $enumDecode(_$PluginTypeEnumMap, json['type']), + name: json['name'] as String, + description: json['description'] as String, + version: json['version'] as String, + author: json['author'] as String, + ); + +Map _$$PluginConfigurationImplToJson( + _$PluginConfigurationImpl instance) => + { + 'type': _$PluginTypeEnumMap[instance.type]!, + 'name': instance.name, + 'description': instance.description, + 'version': instance.version, + 'author': instance.author, + }; + +const _$PluginTypeEnumMap = { + PluginType.metadata: 'metadata', +}; diff --git a/lib/models/metadata/plugin.dart b/lib/models/metadata/plugin.dart new file mode 100644 index 00000000..d6254168 --- /dev/null +++ b/lib/models/metadata/plugin.dart @@ -0,0 +1,21 @@ +part of 'metadata.dart'; + +enum PluginType { metadata } + +@freezed +class PluginConfiguration with _$PluginConfiguration { + const PluginConfiguration._(); + + factory PluginConfiguration({ + required PluginType type, + required String name, + required String description, + required String version, + required String author, + }) = _PluginConfiguration; + + factory PluginConfiguration.fromJson(Map json) => + _$PluginConfigurationFromJson(json); + + String get slug => name.toLowerCase().replaceAll(RegExp(r'[^a-z0-9]+'), '-'); +} diff --git a/lib/services/metadata/apis/localstorage.dart b/lib/services/metadata/apis/localstorage.dart new file mode 100644 index 00000000..3c354d69 --- /dev/null +++ b/lib/services/metadata/apis/localstorage.dart @@ -0,0 +1,60 @@ +import 'package:flutter_js/flutter_js.dart'; +import 'package:shared_preferences/shared_preferences.dart'; + +class PluginLocalStorageApi { + final JavascriptRuntime runtime; + final SharedPreferences sharedPreferences; + + final String pluginName; + + PluginLocalStorageApi({ + required this.runtime, + required this.sharedPreferences, + required this.pluginName, + }) { + runtime.onMessage("LocalStorage.getItem", (args) { + final key = args[0]; + final value = getItem(key); + runtime.evaluate( + """ + eventEmitter.emit('LocalStorage.getItem', ${value != null ? "'$value'" : "null"}); + """, + ); + }); + + runtime.onMessage("LocalStorage.setItem", (args) { + final map = args[0] as Map; + setItem(map["key"], map["value"]); + }); + + runtime.onMessage("LocalStorage.removeItem", (args) { + final map = args[0]; + removeItem(map["key"]); + }); + + runtime.onMessage("LocalStorage.clear", (args) { + clear(); + }); + } + + void setItem(String key, String value) async { + await sharedPreferences.setString("plugin.$pluginName.$key", value); + } + + String? getItem(String key) { + return sharedPreferences.getString("plugin.$pluginName.$key"); + } + + void removeItem(String key) async { + await sharedPreferences.remove("plugin.$pluginName.$key"); + } + + void clear() async { + final keys = sharedPreferences.getKeys(); + for (String key in keys) { + if (key.startsWith("plugin.$pluginName.")) { + await sharedPreferences.remove(key); + } + } + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 611f4720..1188f342 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -4,8 +4,10 @@ import 'dart:convert'; import 'package:flutter_js/extensions/fetch.dart'; import 'package:flutter_js/extensions/xhr.dart'; import 'package:flutter_js/flutter_js.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; +import 'package:spotube/services/metadata/apis/localstorage.dart'; const defaultMetadataLimit = "20"; @@ -13,10 +15,12 @@ const defaultMetadataLimit = "20"; /// objects e.g. SpotubeTrack, SpotubePlaylist, etc. class MetadataApiSignature { final JavascriptRuntime runtime; + final PluginLocalStorageApi localStorageApi; - MetadataApiSignature._(this.runtime); + MetadataApiSignature._(this.runtime, this.localStorageApi); - static Future init(String libraryCode) async { + static Future init( + String libraryCode, PluginConfiguration config) async { final runtime = getJavascriptRuntime(xhr: true).enableXhr(); runtime.enableHandlePromises(); await runtime.enableFetch(); @@ -41,7 +45,17 @@ class MetadataApiSignature { ); } - return MetadataApiSignature._(runtime); + // Create all the PluginAPIs after library code is evaluated + final localStorageApi = PluginLocalStorageApi( + runtime: runtime, + sharedPreferences: await SharedPreferences.getInstance(), + pluginName: config.slug, + ); + + return MetadataApiSignature._( + runtime, + localStorageApi, + ); } void dispose() { From f4306ad1c3523784139ab153cff729c158dea187 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 7 May 2025 23:39:44 +0600 Subject: [PATCH 05/60] feat: add webviiew, totp and setInterval apis for plugins --- lib/collections/routes.dart | 4 + lib/collections/routes.gr.dart | 394 +++++++++++-------- lib/pages/webview/webview.dart | 56 +++ lib/services/metadata/apis/set_interval.dart | 73 ++++ lib/services/metadata/apis/totp.dart | 40 ++ lib/services/metadata/apis/webview.dart | 160 ++++++++ lib/services/metadata/metadata.dart | 56 ++- 7 files changed, 618 insertions(+), 165 deletions(-) create mode 100644 lib/pages/webview/webview.dart create mode 100644 lib/services/metadata/apis/set_interval.dart create mode 100644 lib/services/metadata/apis/totp.dart create mode 100644 lib/services/metadata/apis/webview.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 543bc471..eebedf48 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -231,5 +231,9 @@ class AppRouter extends RootStackRouter { page: LastFMLoginRoute.page, // parentNavigatorKey: rootNavigatorKey, ), + AutoRoute( + path: "/webview", + page: WebviewRoute.page, + ), ]; } diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index 1d608896..93b332b3 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,11 +8,12 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i43; -import 'package:flutter/material.dart' as _i44; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i46; -import 'package:spotify/spotify.dart' as _i45; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i47; +import 'package:auto_route/auto_route.dart' as _i44; +import 'package:flutter/material.dart' as _i45; +import 'package:flutter_inappwebview/flutter_inappwebview.dart' as _i50; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; +import 'package:spotify/spotify.dart' as _i46; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -59,11 +60,13 @@ import 'package:spotube/pages/stats/playlists/playlists.dart' as _i33; import 'package:spotube/pages/stats/stats.dart' as _i32; import 'package:spotube/pages/stats/streams/streams.dart' as _i35; import 'package:spotube/pages/track/track.dart' as _i36; +import 'package:spotube/pages/webview/webview.dart' as _i43; +import 'package:spotube/services/metadata/apis/webview.dart' as _i49; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i43.PageRouteInfo { - const AboutSpotubeRoute({List<_i43.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i44.PageRouteInfo { + const AboutSpotubeRoute({List<_i44.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -71,7 +74,7 @@ class AboutSpotubeRoute extends _i43.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -81,12 +84,12 @@ class AboutSpotubeRoute extends _i43.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i43.PageRouteInfo { +class AlbumRoute extends _i44.PageRouteInfo { AlbumRoute({ - _i44.Key? key, + _i45.Key? key, required String id, - required _i45.AlbumSimple album, - List<_i43.PageRouteInfo>? children, + required _i46.AlbumSimple album, + List<_i44.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -100,7 +103,7 @@ class AlbumRoute extends _i43.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -120,11 +123,11 @@ class AlbumRouteArgs { required this.album, }); - final _i44.Key? key; + final _i45.Key? key; final String id; - final _i45.AlbumSimple album; + final _i46.AlbumSimple album; @override String toString() { @@ -134,11 +137,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i43.PageRouteInfo { +class ArtistRoute extends _i44.PageRouteInfo { ArtistRoute({ required String artistId, - _i44.Key? key, - List<_i43.PageRouteInfo>? children, + _i45.Key? key, + List<_i44.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -151,7 +154,7 @@ class ArtistRoute extends _i43.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -173,7 +176,7 @@ class ArtistRouteArgs { final String artistId; - final _i44.Key? key; + final _i45.Key? key; @override String toString() { @@ -183,8 +186,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i43.PageRouteInfo { - const BlackListRoute({List<_i43.PageRouteInfo>? children}) +class BlackListRoute extends _i44.PageRouteInfo { + const BlackListRoute({List<_i44.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -192,7 +195,7 @@ class BlackListRoute extends _i43.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -202,8 +205,8 @@ class BlackListRoute extends _i43.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i43.PageRouteInfo { - const ConnectControlRoute({List<_i43.PageRouteInfo>? children}) +class ConnectControlRoute extends _i44.PageRouteInfo { + const ConnectControlRoute({List<_i44.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -211,7 +214,7 @@ class ConnectControlRoute extends _i43.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -221,8 +224,8 @@ class ConnectControlRoute extends _i43.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i43.PageRouteInfo { - const ConnectRoute({List<_i43.PageRouteInfo>? children}) +class ConnectRoute extends _i44.PageRouteInfo { + const ConnectRoute({List<_i44.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -230,7 +233,7 @@ class ConnectRoute extends _i43.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -240,8 +243,8 @@ class ConnectRoute extends _i43.PageRouteInfo { /// generated route for /// [_i7.GenrePage] -class GenreRoute extends _i43.PageRouteInfo { - const GenreRoute({List<_i43.PageRouteInfo>? children}) +class GenreRoute extends _i44.PageRouteInfo { + const GenreRoute({List<_i44.PageRouteInfo>? children}) : super( GenreRoute.name, initialChildren: children, @@ -249,7 +252,7 @@ class GenreRoute extends _i43.PageRouteInfo { static const String name = 'GenreRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i7.GenrePage(); @@ -259,12 +262,12 @@ class GenreRoute extends _i43.PageRouteInfo { /// generated route for /// [_i8.GenrePlaylistsPage] -class GenrePlaylistsRoute extends _i43.PageRouteInfo { +class GenrePlaylistsRoute extends _i44.PageRouteInfo { GenrePlaylistsRoute({ - _i44.Key? key, + _i45.Key? key, required String id, - required _i45.Category category, - List<_i43.PageRouteInfo>? children, + required _i46.Category category, + List<_i44.PageRouteInfo>? children, }) : super( GenrePlaylistsRoute.name, args: GenrePlaylistsRouteArgs( @@ -278,7 +281,7 @@ class GenrePlaylistsRoute extends _i43.PageRouteInfo { static const String name = 'GenrePlaylistsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -298,11 +301,11 @@ class GenrePlaylistsRouteArgs { required this.category, }); - final _i44.Key? key; + final _i45.Key? key; final String id; - final _i45.Category category; + final _i46.Category category; @override String toString() { @@ -312,8 +315,8 @@ class GenrePlaylistsRouteArgs { /// generated route for /// [_i9.GettingStartedPage] -class GettingStartedRoute extends _i43.PageRouteInfo { - const GettingStartedRoute({List<_i43.PageRouteInfo>? children}) +class GettingStartedRoute extends _i44.PageRouteInfo { + const GettingStartedRoute({List<_i44.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -321,7 +324,7 @@ class GettingStartedRoute extends _i43.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i9.GettingStartedPage(); @@ -332,11 +335,11 @@ class GettingStartedRoute extends _i43.PageRouteInfo { /// generated route for /// [_i10.HomeFeedSectionPage] class HomeFeedSectionRoute - extends _i43.PageRouteInfo { + extends _i44.PageRouteInfo { HomeFeedSectionRoute({ - _i46.Key? key, + _i47.Key? key, required String sectionUri, - List<_i43.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( HomeFeedSectionRoute.name, args: HomeFeedSectionRouteArgs( @@ -349,7 +352,7 @@ class HomeFeedSectionRoute static const String name = 'HomeFeedSectionRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -370,7 +373,7 @@ class HomeFeedSectionRouteArgs { required this.sectionUri, }); - final _i46.Key? key; + final _i47.Key? key; final String sectionUri; @@ -382,8 +385,8 @@ class HomeFeedSectionRouteArgs { /// generated route for /// [_i11.HomePage] -class HomeRoute extends _i43.PageRouteInfo { - const HomeRoute({List<_i43.PageRouteInfo>? children}) +class HomeRoute extends _i44.PageRouteInfo { + const HomeRoute({List<_i44.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -391,7 +394,7 @@ class HomeRoute extends _i43.PageRouteInfo { static const String name = 'HomeRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i11.HomePage(); @@ -401,8 +404,8 @@ class HomeRoute extends _i43.PageRouteInfo { /// generated route for /// [_i12.LastFMLoginPage] -class LastFMLoginRoute extends _i43.PageRouteInfo { - const LastFMLoginRoute({List<_i43.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i44.PageRouteInfo { + const LastFMLoginRoute({List<_i44.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -410,7 +413,7 @@ class LastFMLoginRoute extends _i43.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i12.LastFMLoginPage(); @@ -420,8 +423,8 @@ class LastFMLoginRoute extends _i43.PageRouteInfo { /// generated route for /// [_i13.LibraryPage] -class LibraryRoute extends _i43.PageRouteInfo { - const LibraryRoute({List<_i43.PageRouteInfo>? children}) +class LibraryRoute extends _i44.PageRouteInfo { + const LibraryRoute({List<_i44.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -429,7 +432,7 @@ class LibraryRoute extends _i43.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i13.LibraryPage(); @@ -439,11 +442,11 @@ class LibraryRoute extends _i43.PageRouteInfo { /// generated route for /// [_i14.LikedPlaylistPage] -class LikedPlaylistRoute extends _i43.PageRouteInfo { +class LikedPlaylistRoute extends _i44.PageRouteInfo { LikedPlaylistRoute({ - _i44.Key? key, - required _i45.PlaylistSimple playlist, - List<_i43.PageRouteInfo>? children, + _i45.Key? key, + required _i46.PlaylistSimple playlist, + List<_i44.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -455,7 +458,7 @@ class LikedPlaylistRoute extends _i43.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -473,9 +476,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i44.Key? key; + final _i45.Key? key; - final _i45.PlaylistSimple playlist; + final _i46.PlaylistSimple playlist; @override String toString() { @@ -485,13 +488,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i15.LocalLibraryPage] -class LocalLibraryRoute extends _i43.PageRouteInfo { +class LocalLibraryRoute extends _i44.PageRouteInfo { LocalLibraryRoute({ required String location, - _i44.Key? key, + _i45.Key? key, bool isDownloads = false, bool isCache = false, - List<_i43.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -505,7 +508,7 @@ class LocalLibraryRoute extends _i43.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -529,7 +532,7 @@ class LocalLibraryRouteArgs { final String location; - final _i44.Key? key; + final _i45.Key? key; final bool isDownloads; @@ -543,8 +546,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i16.LogsPage] -class LogsRoute extends _i43.PageRouteInfo { - const LogsRoute({List<_i43.PageRouteInfo>? children}) +class LogsRoute extends _i44.PageRouteInfo { + const LogsRoute({List<_i44.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -552,7 +555,7 @@ class LogsRoute extends _i43.PageRouteInfo { static const String name = 'LogsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i16.LogsPage(); @@ -562,8 +565,8 @@ class LogsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i17.LyricsPage] -class LyricsRoute extends _i43.PageRouteInfo { - const LyricsRoute({List<_i43.PageRouteInfo>? children}) +class LyricsRoute extends _i44.PageRouteInfo { + const LyricsRoute({List<_i44.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -571,7 +574,7 @@ class LyricsRoute extends _i43.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i17.LyricsPage(); @@ -581,11 +584,11 @@ class LyricsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i18.MiniLyricsPage] -class MiniLyricsRoute extends _i43.PageRouteInfo { +class MiniLyricsRoute extends _i44.PageRouteInfo { MiniLyricsRoute({ - _i46.Key? key, - required _i46.Size prevSize, - List<_i43.PageRouteInfo>? children, + _i47.Key? key, + required _i47.Size prevSize, + List<_i44.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -597,7 +600,7 @@ class MiniLyricsRoute extends _i43.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -615,9 +618,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i46.Key? key; + final _i47.Key? key; - final _i46.Size prevSize; + final _i47.Size prevSize; @override String toString() { @@ -627,8 +630,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i19.PlayerLyricsPage] -class PlayerLyricsRoute extends _i43.PageRouteInfo { - const PlayerLyricsRoute({List<_i43.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i44.PageRouteInfo { + const PlayerLyricsRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -636,7 +639,7 @@ class PlayerLyricsRoute extends _i43.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i19.PlayerLyricsPage(); @@ -646,8 +649,8 @@ class PlayerLyricsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i20.PlayerQueuePage] -class PlayerQueueRoute extends _i43.PageRouteInfo { - const PlayerQueueRoute({List<_i43.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i44.PageRouteInfo { + const PlayerQueueRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -655,7 +658,7 @@ class PlayerQueueRoute extends _i43.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i20.PlayerQueuePage(); @@ -665,8 +668,8 @@ class PlayerQueueRoute extends _i43.PageRouteInfo { /// generated route for /// [_i21.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i43.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i43.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -674,7 +677,7 @@ class PlayerTrackSourcesRoute extends _i43.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i21.PlayerTrackSourcesPage(); @@ -685,11 +688,11 @@ class PlayerTrackSourcesRoute extends _i43.PageRouteInfo { /// generated route for /// [_i22.PlaylistGenerateResultPage] class PlaylistGenerateResultRoute - extends _i43.PageRouteInfo { + extends _i44.PageRouteInfo { PlaylistGenerateResultRoute({ - _i46.Key? key, - required _i47.GeneratePlaylistProviderInput state, - List<_i43.PageRouteInfo>? children, + _i47.Key? key, + required _i48.GeneratePlaylistProviderInput state, + List<_i44.PageRouteInfo>? children, }) : super( PlaylistGenerateResultRoute.name, args: PlaylistGenerateResultRouteArgs( @@ -701,7 +704,7 @@ class PlaylistGenerateResultRoute static const String name = 'PlaylistGenerateResultRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -719,9 +722,9 @@ class PlaylistGenerateResultRouteArgs { required this.state, }); - final _i46.Key? key; + final _i47.Key? key; - final _i47.GeneratePlaylistProviderInput state; + final _i48.GeneratePlaylistProviderInput state; @override String toString() { @@ -731,8 +734,8 @@ class PlaylistGenerateResultRouteArgs { /// generated route for /// [_i23.PlaylistGeneratorPage] -class PlaylistGeneratorRoute extends _i43.PageRouteInfo { - const PlaylistGeneratorRoute({List<_i43.PageRouteInfo>? children}) +class PlaylistGeneratorRoute extends _i44.PageRouteInfo { + const PlaylistGeneratorRoute({List<_i44.PageRouteInfo>? children}) : super( PlaylistGeneratorRoute.name, initialChildren: children, @@ -740,7 +743,7 @@ class PlaylistGeneratorRoute extends _i43.PageRouteInfo { static const String name = 'PlaylistGeneratorRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i23.PlaylistGeneratorPage(); @@ -750,12 +753,12 @@ class PlaylistGeneratorRoute extends _i43.PageRouteInfo { /// generated route for /// [_i24.PlaylistPage] -class PlaylistRoute extends _i43.PageRouteInfo { +class PlaylistRoute extends _i44.PageRouteInfo { PlaylistRoute({ - _i44.Key? key, + _i45.Key? key, required String id, - required _i45.PlaylistSimple playlist, - List<_i43.PageRouteInfo>? children, + required _i46.PlaylistSimple playlist, + List<_i44.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -769,7 +772,7 @@ class PlaylistRoute extends _i43.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -789,11 +792,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i44.Key? key; + final _i45.Key? key; final String id; - final _i45.PlaylistSimple playlist; + final _i46.PlaylistSimple playlist; @override String toString() { @@ -803,8 +806,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i25.ProfilePage] -class ProfileRoute extends _i43.PageRouteInfo { - const ProfileRoute({List<_i43.PageRouteInfo>? children}) +class ProfileRoute extends _i44.PageRouteInfo { + const ProfileRoute({List<_i44.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -812,7 +815,7 @@ class ProfileRoute extends _i43.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i25.ProfilePage(); @@ -822,8 +825,8 @@ class ProfileRoute extends _i43.PageRouteInfo { /// generated route for /// [_i26.RootAppPage] -class RootAppRoute extends _i43.PageRouteInfo { - const RootAppRoute({List<_i43.PageRouteInfo>? children}) +class RootAppRoute extends _i44.PageRouteInfo { + const RootAppRoute({List<_i44.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -831,7 +834,7 @@ class RootAppRoute extends _i43.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i26.RootAppPage(); @@ -841,8 +844,8 @@ class RootAppRoute extends _i43.PageRouteInfo { /// generated route for /// [_i27.SearchPage] -class SearchRoute extends _i43.PageRouteInfo { - const SearchRoute({List<_i43.PageRouteInfo>? children}) +class SearchRoute extends _i44.PageRouteInfo { + const SearchRoute({List<_i44.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -850,7 +853,7 @@ class SearchRoute extends _i43.PageRouteInfo { static const String name = 'SearchRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i27.SearchPage(); @@ -860,8 +863,8 @@ class SearchRoute extends _i43.PageRouteInfo { /// generated route for /// [_i28.SettingsPage] -class SettingsRoute extends _i43.PageRouteInfo { - const SettingsRoute({List<_i43.PageRouteInfo>? children}) +class SettingsRoute extends _i44.PageRouteInfo { + const SettingsRoute({List<_i44.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -869,7 +872,7 @@ class SettingsRoute extends _i43.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i28.SettingsPage(); @@ -879,8 +882,8 @@ class SettingsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i29.StatsAlbumsPage] -class StatsAlbumsRoute extends _i43.PageRouteInfo { - const StatsAlbumsRoute({List<_i43.PageRouteInfo>? children}) +class StatsAlbumsRoute extends _i44.PageRouteInfo { + const StatsAlbumsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -888,7 +891,7 @@ class StatsAlbumsRoute extends _i43.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i29.StatsAlbumsPage(); @@ -898,8 +901,8 @@ class StatsAlbumsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i30.StatsArtistsPage] -class StatsArtistsRoute extends _i43.PageRouteInfo { - const StatsArtistsRoute({List<_i43.PageRouteInfo>? children}) +class StatsArtistsRoute extends _i44.PageRouteInfo { + const StatsArtistsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -907,7 +910,7 @@ class StatsArtistsRoute extends _i43.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i30.StatsArtistsPage(); @@ -917,8 +920,8 @@ class StatsArtistsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i31.StatsMinutesPage] -class StatsMinutesRoute extends _i43.PageRouteInfo { - const StatsMinutesRoute({List<_i43.PageRouteInfo>? children}) +class StatsMinutesRoute extends _i44.PageRouteInfo { + const StatsMinutesRoute({List<_i44.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -926,7 +929,7 @@ class StatsMinutesRoute extends _i43.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i31.StatsMinutesPage(); @@ -936,8 +939,8 @@ class StatsMinutesRoute extends _i43.PageRouteInfo { /// generated route for /// [_i32.StatsPage] -class StatsRoute extends _i43.PageRouteInfo { - const StatsRoute({List<_i43.PageRouteInfo>? children}) +class StatsRoute extends _i44.PageRouteInfo { + const StatsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -945,7 +948,7 @@ class StatsRoute extends _i43.PageRouteInfo { static const String name = 'StatsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i32.StatsPage(); @@ -955,8 +958,8 @@ class StatsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i33.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i43.PageRouteInfo { - const StatsPlaylistsRoute({List<_i43.PageRouteInfo>? children}) +class StatsPlaylistsRoute extends _i44.PageRouteInfo { + const StatsPlaylistsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -964,7 +967,7 @@ class StatsPlaylistsRoute extends _i43.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i33.StatsPlaylistsPage(); @@ -974,8 +977,8 @@ class StatsPlaylistsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i34.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i43.PageRouteInfo { - const StatsStreamFeesRoute({List<_i43.PageRouteInfo>? children}) +class StatsStreamFeesRoute extends _i44.PageRouteInfo { + const StatsStreamFeesRoute({List<_i44.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -983,7 +986,7 @@ class StatsStreamFeesRoute extends _i43.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i34.StatsStreamFeesPage(); @@ -993,8 +996,8 @@ class StatsStreamFeesRoute extends _i43.PageRouteInfo { /// generated route for /// [_i35.StatsStreamsPage] -class StatsStreamsRoute extends _i43.PageRouteInfo { - const StatsStreamsRoute({List<_i43.PageRouteInfo>? children}) +class StatsStreamsRoute extends _i44.PageRouteInfo { + const StatsStreamsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -1002,7 +1005,7 @@ class StatsStreamsRoute extends _i43.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i35.StatsStreamsPage(); @@ -1012,11 +1015,11 @@ class StatsStreamsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i36.TrackPage] -class TrackRoute extends _i43.PageRouteInfo { +class TrackRoute extends _i44.PageRouteInfo { TrackRoute({ - _i46.Key? key, + _i47.Key? key, required String trackId, - List<_i43.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -1029,7 +1032,7 @@ class TrackRoute extends _i43.PageRouteInfo { static const String name = 'TrackRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -1049,7 +1052,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i46.Key? key; + final _i47.Key? key; final String trackId; @@ -1061,8 +1064,8 @@ class TrackRouteArgs { /// generated route for /// [_i37.UserAlbumsPage] -class UserAlbumsRoute extends _i43.PageRouteInfo { - const UserAlbumsRoute({List<_i43.PageRouteInfo>? children}) +class UserAlbumsRoute extends _i44.PageRouteInfo { + const UserAlbumsRoute({List<_i44.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -1070,7 +1073,7 @@ class UserAlbumsRoute extends _i43.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i37.UserAlbumsPage(); @@ -1080,8 +1083,8 @@ class UserAlbumsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i38.UserArtistsPage] -class UserArtistsRoute extends _i43.PageRouteInfo { - const UserArtistsRoute({List<_i43.PageRouteInfo>? children}) +class UserArtistsRoute extends _i44.PageRouteInfo { + const UserArtistsRoute({List<_i44.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -1089,7 +1092,7 @@ class UserArtistsRoute extends _i43.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i38.UserArtistsPage(); @@ -1099,8 +1102,8 @@ class UserArtistsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i39.UserDownloadsPage] -class UserDownloadsRoute extends _i43.PageRouteInfo { - const UserDownloadsRoute({List<_i43.PageRouteInfo>? children}) +class UserDownloadsRoute extends _i44.PageRouteInfo { + const UserDownloadsRoute({List<_i44.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -1108,7 +1111,7 @@ class UserDownloadsRoute extends _i43.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i39.UserDownloadsPage(); @@ -1118,8 +1121,8 @@ class UserDownloadsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i40.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i43.PageRouteInfo { - const UserLocalLibraryRoute({List<_i43.PageRouteInfo>? children}) +class UserLocalLibraryRoute extends _i44.PageRouteInfo { + const UserLocalLibraryRoute({List<_i44.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1127,7 +1130,7 @@ class UserLocalLibraryRoute extends _i43.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i40.UserLocalLibraryPage(); @@ -1137,8 +1140,8 @@ class UserLocalLibraryRoute extends _i43.PageRouteInfo { /// generated route for /// [_i41.UserPlaylistsPage] -class UserPlaylistsRoute extends _i43.PageRouteInfo { - const UserPlaylistsRoute({List<_i43.PageRouteInfo>? children}) +class UserPlaylistsRoute extends _i44.PageRouteInfo { + const UserPlaylistsRoute({List<_i44.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1146,7 +1149,7 @@ class UserPlaylistsRoute extends _i43.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i41.UserPlaylistsPage(); @@ -1156,8 +1159,8 @@ class UserPlaylistsRoute extends _i43.PageRouteInfo { /// generated route for /// [_i42.WebViewLoginPage] -class WebViewLoginRoute extends _i43.PageRouteInfo { - const WebViewLoginRoute({List<_i43.PageRouteInfo>? children}) +class WebViewLoginRoute extends _i44.PageRouteInfo { + const WebViewLoginRoute({List<_i44.PageRouteInfo>? children}) : super( WebViewLoginRoute.name, initialChildren: children, @@ -1165,10 +1168,75 @@ class WebViewLoginRoute extends _i43.PageRouteInfo { static const String name = 'WebViewLoginRoute'; - static _i43.PageInfo page = _i43.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i42.WebViewLoginPage(); }, ); } + +/// generated route for +/// [_i43.WebviewPage] +class WebviewRoute extends _i44.PageRouteInfo { + WebviewRoute({ + _i47.Key? key, + _i49.WebviewInitialSettings? initialSettings, + String? url, + void Function( + _i50.InAppWebViewController, + _i50.WebUri?, + )? onLoadStop, + List<_i44.PageRouteInfo>? children, + }) : super( + WebviewRoute.name, + args: WebviewRouteArgs( + key: key, + initialSettings: initialSettings, + url: url, + onLoadStop: onLoadStop, + ), + initialChildren: children, + ); + + static const String name = 'WebviewRoute'; + + static _i44.PageInfo page = _i44.PageInfo( + name, + builder: (data) { + final args = + data.argsAs(orElse: () => const WebviewRouteArgs()); + return _i43.WebviewPage( + key: args.key, + initialSettings: args.initialSettings, + url: args.url, + onLoadStop: args.onLoadStop, + ); + }, + ); +} + +class WebviewRouteArgs { + const WebviewRouteArgs({ + this.key, + this.initialSettings, + this.url, + this.onLoadStop, + }); + + final _i47.Key? key; + + final _i49.WebviewInitialSettings? initialSettings; + + final String? url; + + final void Function( + _i50.InAppWebViewController, + _i50.WebUri?, + )? onLoadStop; + + @override + String toString() { + return 'WebviewRouteArgs{key: $key, initialSettings: $initialSettings, url: $url, onLoadStop: $onLoadStop}'; + } +} diff --git a/lib/pages/webview/webview.dart b/lib/pages/webview/webview.dart new file mode 100644 index 00000000..b4ce47b3 --- /dev/null +++ b/lib/pages/webview/webview.dart @@ -0,0 +1,56 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/components/button/back_button.dart'; +import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/services/metadata/apis/webview.dart'; + +@RoutePage() +class WebviewPage extends StatelessWidget { + final WebviewInitialSettings? initialSettings; + final String? url; + final void Function(InAppWebViewController controller, WebUri? url)? + onLoadStop; + + const WebviewPage({ + super.key, + this.initialSettings, + this.url, + this.onLoadStop, + }); + + @override + Widget build(BuildContext context) { + return SafeArea( + bottom: false, + child: Scaffold( + headers: const [ + TitleBar( + leading: [BackButton(color: Colors.white)], + backgroundColor: Colors.transparent, + ), + ], + floatingHeader: true, + child: InAppWebView( + initialSettings: InAppWebViewSettings( + userAgent: initialSettings?.userAgent ?? + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36", + incognito: initialSettings?.incognito ?? false, + clearCache: initialSettings?.clearCache ?? false, + clearSessionCache: initialSettings?.clearSessionCache ?? false, + ), + initialUrlRequest: URLRequest( + url: WebUri("https://accounts.spotify.com/"), + ), + onPermissionRequest: (controller, permissionRequest) async { + return PermissionResponse( + resources: permissionRequest.resources, + action: PermissionResponseAction.GRANT, + ); + }, + onLoadStop: onLoadStop, + ), + ), + ); + } +} diff --git a/lib/services/metadata/apis/set_interval.dart b/lib/services/metadata/apis/set_interval.dart new file mode 100644 index 00000000..349fbdeb --- /dev/null +++ b/lib/services/metadata/apis/set_interval.dart @@ -0,0 +1,73 @@ +import 'dart:async'; + +import 'package:flutter_js/flutter_js.dart'; + +class PluginSetIntervalApi { + final JavascriptRuntime runtime; + + final Map _timers = {}; + + PluginSetIntervalApi(this.runtime) { + runtime.evaluate( + """ + var __NATIVE_FLUTTER_JS__setIntervalCount = -1; + var __NATIVE_FLUTTER_JS__setIntervalCallbacks = {}; + function setInterval(fnInterval, interval) { + try { + __NATIVE_FLUTTER_JS__setIntervalCount += 1; + var intervalIndex = '' + __NATIVE_FLUTTER_JS__setIntervalCount; + __NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex] = fnInterval; + ; + sendMessage('PluginSetIntervalApi.setInterval', JSON.stringify({ intervalIndex, interval})); + return intervalIndex; + } catch (e) { + console.error('ERROR HERE',e.message); + } + }; + + function clearInterval(intervalIndex) { + try { + delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex]; + sendMessage('PluginSetIntervalApi.clearInterval', JSON.stringify({ intervalIndex})); + } catch (e) { + console.error('ERROR HERE',e.message); + } + }; + 1 + """, + ); + + runtime.onMessage('PluginSetIntervalApi.setInterval', (dynamic args) { + try { + int duration = args['interval'] ?? 0; + String idx = args['intervalIndex']; + + _timers[idx] = + Timer.periodic(Duration(milliseconds: duration), (timer) { + runtime.evaluate(""" + __NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx].call(); + delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx]; + """); + }); + } on Exception catch (e) { + print('Exception no setInterval: $e'); + } on Error catch (e) { + print('Erro no setInterval: $e'); + } + }); + + runtime.onMessage('PluginSetIntervalApi.clearInterval', (dynamic args) { + try { + String idx = args['intervalIndex']; + if (_timers.containsKey(idx)) { + _timers[idx]?.cancel(); + _timers.remove(idx); + } + } on Exception catch (e) { + print('Exception no clearInterval: $e'); + } on Error catch (e) { + print('Erro no clearInterval: $e'); + } + }); + } +} diff --git a/lib/services/metadata/apis/totp.dart b/lib/services/metadata/apis/totp.dart new file mode 100644 index 00000000..7dad4f78 --- /dev/null +++ b/lib/services/metadata/apis/totp.dart @@ -0,0 +1,40 @@ +import 'package:flutter_js/javascript_runtime.dart'; +import 'package:otp_util/otp_util.dart'; +// ignore: implementation_imports +import 'package:otp_util/src/utils/generic_util.dart'; + +class PluginTotpGenerator { + final JavascriptRuntime runtime; + + PluginTotpGenerator(this.runtime) { + runtime.onMessage("TotpGenerator.generate", (args) { + final opts = args[0]; + if (opts is! Map) { + return; + } + + final totp = TOTP( + secret: opts["secret"] as String, + algorithm: OTPAlgorithm.values.firstWhere( + (e) => e.name == opts["algorithm"], + orElse: () => OTPAlgorithm.SHA1, + ), + digits: opts["digits"] as int? ?? 6, + interval: opts["interval"] as int? ?? 30, + ); + + final otp = totp.generateOTP( + input: Util.timeFormat( + time: DateTime.fromMillisecondsSinceEpoch(opts["period"]), + interval: 30, + ), + ); + + runtime.evaluate( + """ + eventEmitter.emit('Totp.generate', '$otp'); + """, + ); + }); + } +} diff --git a/lib/services/metadata/apis/webview.dart b/lib/services/metadata/apis/webview.dart new file mode 100644 index 00000000..630324a0 --- /dev/null +++ b/lib/services/metadata/apis/webview.dart @@ -0,0 +1,160 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:auto_route/auto_route.dart'; +import 'package:desktop_webview_window/desktop_webview_window.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:flutter_js/flutter_js.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/pages/mobile_login/no_webview_runtime_dialog.dart'; +import 'package:spotube/utils/platform.dart'; + +class WebviewInitialSettings { + final String? userAgent; + final bool? incognito; + final bool? clearCache; + final bool? clearSessionCache; + + WebviewInitialSettings({ + this.userAgent, + this.incognito, + this.clearCache, + this.clearSessionCache, + }); + + factory WebviewInitialSettings.fromJson(Map json) { + return WebviewInitialSettings( + userAgent: json["userAgent"], + incognito: json["incognito"], + clearCache: json["clearCache"], + clearSessionCache: json["clearSessionCache"], + ); + } +} + +class PluginWebViewApi { + JavascriptRuntime runtime; + + PluginWebViewApi({ + required this.runtime, + }) { + runtime.onMessage("WebView.show", (args) { + if (args[0] is! Map) { + return; + } + showWebView( + url: args[0]["url"] as String, + initialSettings: WebviewInitialSettings.fromJson( + args[0]["initialSettings"], + ), + ); + }); + } + + Future showWebView({ + required String url, + WebviewInitialSettings? initialSettings, + }) async { + if (rootNavigatorKey.currentContext == null) { + return; + } + final context = rootNavigatorKey.currentContext!; + final theme = Theme.of(context); + + if (kIsMobile || kIsMacOS) { + context.pushRoute(WebviewRoute( + initialSettings: initialSettings, + url: url, + onLoadStop: (controller, uri) async { + if (uri == null) return; + final cookies = await CookieManager().getAllCookies(); + + final jsonCookies = cookies.map((e) { + return { + "name": e.name, + "value": e.value, + "domain": e.domain, + "path": e.path, + }; + }); + + runtime.onMessage("WebView.close", (args) { + context.back(); + }); + + runtime.evaluate( + """ + eventEmitter.emit('WebView.onLoadFinish', {url: '${uri.toString()}', cookies: ${jsonEncode(jsonCookies)}}); + """, + ); + }, + )); + return; + } + + try { + final applicationSupportDir = await getApplicationSupportDirectory(); + final userDataFolder = Directory( + join(applicationSupportDir.path, "webview_window_Webview2"), + ); + + if (!await userDataFolder.exists()) { + await userDataFolder.create(); + } + + final webview = await WebviewWindow.create( + configuration: CreateConfiguration( + title: "Webview", + titleBarTopPadding: kIsMacOS ? 20 : 0, + windowHeight: 720, + windowWidth: 1280, + userDataFolderWindows: userDataFolder.path, + ), + ); + + runtime.onMessage("WebView.close", (args) { + webview.close(); + }); + + webview + ..setBrightness(theme.colorScheme.brightness) + ..launch(url) + ..setOnUrlRequestCallback((url) { + () async { + final cookies = await webview.getAllCookies(); + final jsonCookies = cookies.map((e) { + return { + "name": e.name, + "value": e.value, + "domain": e.domain, + "path": e.path, + }; + }); + + runtime.evaluate( + """ + eventEmitter.emit('WebView.onLoadFinish', {url: '$url', cookies: ${jsonEncode(jsonCookies)}}); + """, + ); + }(); + return false; + }); + } on PlatformException catch (_) { + if (!await WebviewWindow.isWebviewAvailable()) { + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + showDialog( + context: context, + builder: (context) { + return const NoWebviewRuntimeDialog(); + }, + ); + }); + } + } + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 1188f342..a24d49b1 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -8,16 +8,45 @@ import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; +import 'package:spotube/services/metadata/apis/set_interval.dart'; +import 'package:spotube/services/metadata/apis/totp.dart'; +import 'package:spotube/services/metadata/apis/webview.dart'; const defaultMetadataLimit = "20"; +class MetadataSignatureFlags { + final bool requiresAuth; + + const MetadataSignatureFlags({ + this.requiresAuth = false, + }); + + factory MetadataSignatureFlags.fromJson(Map json) { + return MetadataSignatureFlags( + requiresAuth: json["requiresAuth"] ?? false, + ); + } +} + /// Signature for metadata and related methods that will return Spotube native /// objects e.g. SpotubeTrack, SpotubePlaylist, etc. class MetadataApiSignature { final JavascriptRuntime runtime; final PluginLocalStorageApi localStorageApi; + final PluginWebViewApi webViewApi; + final PluginTotpGenerator totpGenerator; + final PluginSetIntervalApi setIntervalApi; + late MetadataSignatureFlags _signatureFlags; - MetadataApiSignature._(this.runtime, this.localStorageApi); + MetadataSignatureFlags get signatureFlags => _signatureFlags; + + MetadataApiSignature._( + this.runtime, + this.localStorageApi, + this.webViewApi, + this.totpGenerator, + this.setIntervalApi, + ); static Future init( String libraryCode, PluginConfiguration config) async { @@ -52,10 +81,21 @@ class MetadataApiSignature { pluginName: config.slug, ); - return MetadataApiSignature._( + final webViewApi = PluginWebViewApi(runtime: runtime); + final totpGenerator = PluginTotpGenerator(runtime); + final setIntervalApi = PluginSetIntervalApi(runtime); + + final metadataApi = MetadataApiSignature._( runtime, localStorageApi, + webViewApi, + totpGenerator, + setIntervalApi, ); + + metadataApi._signatureFlags = await metadataApi._getSignatureFlags(); + + return metadataApi; } void dispose() { @@ -104,6 +144,18 @@ class MetadataApiSignature { return completer.future; } + Future _getSignatureFlags() async { + final res = await invoke("metadataApi.getSignatureFlags"); + + return MetadataSignatureFlags.fromJson(res); + } + + // ----- Authentication ------ + + Future authenticate() async { + await invoke("metadataApi.authenticate"); + } + // ----- Track ------ Future getTrack(String id) async { final result = await invoke("metadataApi.getTrack", [id]); From bb0afa5a0cdfb109e358d25c641602fa6c107849 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 9 May 2025 23:02:55 +0600 Subject: [PATCH 06/60] feat: enhance local storage and webview APIs with improved error handling and resource management --- lib/services/metadata/apis/localstorage.dart | 2 +- lib/services/metadata/apis/set_interval.dart | 7 ++++ lib/services/metadata/apis/totp.dart | 2 +- lib/services/metadata/apis/webview.dart | 19 ++++++++--- lib/services/metadata/metadata.dart | 35 ++++++++++++-------- 5 files changed, 45 insertions(+), 20 deletions(-) diff --git a/lib/services/metadata/apis/localstorage.dart b/lib/services/metadata/apis/localstorage.dart index 3c354d69..3b76f169 100644 --- a/lib/services/metadata/apis/localstorage.dart +++ b/lib/services/metadata/apis/localstorage.dart @@ -13,7 +13,7 @@ class PluginLocalStorageApi { required this.pluginName, }) { runtime.onMessage("LocalStorage.getItem", (args) { - final key = args[0]; + final key = args[0]["key"]; final value = getItem(key); runtime.evaluate( """ diff --git a/lib/services/metadata/apis/set_interval.dart b/lib/services/metadata/apis/set_interval.dart index 349fbdeb..1b1aba97 100644 --- a/lib/services/metadata/apis/set_interval.dart +++ b/lib/services/metadata/apis/set_interval.dart @@ -70,4 +70,11 @@ class PluginSetIntervalApi { } }); } + + void dispose() { + for (var timer in _timers.values) { + timer.cancel(); + } + _timers.clear(); + } } diff --git a/lib/services/metadata/apis/totp.dart b/lib/services/metadata/apis/totp.dart index 7dad4f78..7a0bdb90 100644 --- a/lib/services/metadata/apis/totp.dart +++ b/lib/services/metadata/apis/totp.dart @@ -32,7 +32,7 @@ class PluginTotpGenerator { runtime.evaluate( """ - eventEmitter.emit('Totp.generate', '$otp'); + eventEmitter.emit('TotpGenerator.generate', '$otp'); """, ); }); diff --git a/lib/services/metadata/apis/webview.dart b/lib/services/metadata/apis/webview.dart index 630324a0..5ad74060 100644 --- a/lib/services/metadata/apis/webview.dart +++ b/lib/services/metadata/apis/webview.dart @@ -49,13 +49,17 @@ class PluginWebViewApi { } showWebView( url: args[0]["url"] as String, - initialSettings: WebviewInitialSettings.fromJson( - args[0]["initialSettings"], - ), + initialSettings: args[0]["initialSettings"] != null + ? WebviewInitialSettings.fromJson( + args[0]["initialSettings"], + ) + : null, ); }); } + Webview? webviewWindow; + Future showWebView({ required String url, WebviewInitialSettings? initialSettings, @@ -117,6 +121,8 @@ class PluginWebViewApi { ), ); + webviewWindow = webview; + runtime.onMessage("WebView.close", (args) { webview.close(); }); @@ -134,7 +140,7 @@ class PluginWebViewApi { "domain": e.domain, "path": e.path, }; - }); + }).toList(); runtime.evaluate( """ @@ -157,4 +163,9 @@ class PluginWebViewApi { } } } + + void dispose() { + webviewWindow?.close(); + webviewWindow = null; + } } diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index a24d49b1..7b151fa9 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -61,19 +61,6 @@ class MetadataApiSignature { }, ); - final res = runtime.evaluate( - """ - ;$libraryCode; - const metadataApi = new MetadataApi(); - """, - ); - - if (res.isError) { - AppLogger.reportError( - "Error evaluating code: $libraryCode\n${res.rawResult}", - ); - } - // Create all the PluginAPIs after library code is evaluated final localStorageApi = PluginLocalStorageApi( runtime: runtime, @@ -93,12 +80,26 @@ class MetadataApiSignature { setIntervalApi, ); + final res = runtime.evaluate( + """ + ;$libraryCode; + const metadataApi = new MetadataApi(); + """, + ); metadataApi._signatureFlags = await metadataApi._getSignatureFlags(); + if (res.isError) { + AppLogger.reportError( + "Error evaluating code: $libraryCode\n${res.rawResult}", + ); + } + return metadataApi; } void dispose() { + setIntervalApi.dispose(); + webViewApi.dispose(); runtime.dispose(); } @@ -119,7 +120,7 @@ class MetadataApiSignature { $method(...${args != null ? jsonEncode(args) : "[]"}) .then((res) => { try { - sendMessage("$method", JSON.stringify(res)); + sendMessage("$method", res ? JSON.stringify(res) : "[]"); } catch (e) { console.error("Failed to send message in $method.then: ", `\${e.toString()}\n\${e.stack.toString()}`); } @@ -497,6 +498,12 @@ class MetadataApiSignature { } // ----- User ------ + Future getMe() async { + final res = await invoke("metadataApi.getMe"); + + return SpotubeUserObject.fromJson(res); + } + Future followArtist(String userId, String artistId) async { await invoke("metadataApi.followArtist", [userId, artistId]); } From 67713c60d4bc108ddbdbb57b20ba300d8a3db7a2 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 9 May 2025 23:30:57 +0600 Subject: [PATCH 07/60] feat(metadata_plugin): add logout method --- lib/services/metadata/metadata.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 7b151fa9..57a38bc2 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -157,6 +157,10 @@ class MetadataApiSignature { await invoke("metadataApi.authenticate"); } + Future logout() async { + await invoke("metadataApi.logout"); + } + // ----- Track ------ Future getTrack(String id) async { final result = await invoke("metadataApi.getTrack", [id]); From 2d6fe886e2b79225507bd8e629fc7cb64c1a6c2e Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 13 Jun 2025 16:23:41 +0600 Subject: [PATCH 08/60] chore: metadata --- drift_schemas/app_db/drift_schema_v7.json | 1 + lib/collections/routes.dart | 4 + lib/collections/routes.gr.dart | 468 +- lib/collections/spotube_icons.dart | 3 + lib/main.dart | 3 + lib/models/database/database.dart | 25 +- lib/models/database/database.g.dart | 540 +++ lib/models/database/database.steps.dart | 280 ++ .../database/tables/metadata_plugins.dart | 10 + lib/pages/settings/metadata_plugins.dart | 182 + lib/pages/settings/sections/accounts.dart | 95 +- lib/provider/metadata_plugin/auth.dart | 49 + .../metadata_plugin_provider.dart | 365 ++ lib/services/metadata/apis/set_interval.dart | 2 +- lib/services/metadata/metadata.dart | 28 +- pubspec.lock | 2 +- pubspec.yaml | 1 + test/drift/app_db/generated/schema.dart | 5 +- test/drift/app_db/generated/schema_v7.dart | 3758 +++++++++++++++++ 19 files changed, 5500 insertions(+), 321 deletions(-) create mode 100644 drift_schemas/app_db/drift_schema_v7.json create mode 100644 lib/models/database/tables/metadata_plugins.dart create mode 100644 lib/pages/settings/metadata_plugins.dart create mode 100644 lib/provider/metadata_plugin/auth.dart create mode 100644 lib/provider/metadata_plugin/metadata_plugin_provider.dart create mode 100644 test/drift/app_db/generated/schema_v7.dart diff --git a/drift_schemas/app_db/drift_schema_v7.json b/drift_schemas/app_db/drift_schema_v7.json new file mode 100644 index 00000000..d42e0b84 --- /dev/null +++ b/drift_schemas/app_db/drift_schema_v7.json @@ -0,0 +1 @@ +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[6],"type":"table","data":{"name":"playlist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_player_state_id","getter_name":"audioPlayerStateId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES audio_player_state_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES audio_player_state_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"index","getter_name":"index","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[7],"type":"table","data":{"name":"playlist_media_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playlist_id","getter_name":"playlistId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES playlist_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES playlist_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"uri","getter_name":"uri","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"extras","getter_name":"extras","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}},{"name":"http_headers","getter_name":"httpHeaders","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":11,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":12,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":13,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index eebedf48..3f2043a1 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -102,6 +102,10 @@ class AppRouter extends RootStackRouter { path: "settings", page: SettingsRoute.page, ), + AutoRoute( + path: "settings/metadata-provider", + page: SettingsMetadataProviderRoute.page, + ), AutoRoute( path: "settings/blacklist", page: BlackListRoute.page, diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index 93b332b3..a049fd28 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,12 +8,12 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i44; -import 'package:flutter/material.dart' as _i45; -import 'package:flutter_inappwebview/flutter_inappwebview.dart' as _i50; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; -import 'package:spotify/spotify.dart' as _i46; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; +import 'package:auto_route/auto_route.dart' as _i45; +import 'package:flutter/material.dart' as _i46; +import 'package:flutter_inappwebview/flutter_inappwebview.dart' as _i51; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48; +import 'package:spotify/spotify.dart' as _i47; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i49; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -29,17 +29,17 @@ import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart' as _i23; import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart' as _i22; -import 'package:spotube/pages/library/user_albums.dart' as _i37; -import 'package:spotube/pages/library/user_artists.dart' as _i38; -import 'package:spotube/pages/library/user_downloads.dart' as _i39; +import 'package:spotube/pages/library/user_albums.dart' as _i38; +import 'package:spotube/pages/library/user_artists.dart' as _i39; +import 'package:spotube/pages/library/user_downloads.dart' as _i40; import 'package:spotube/pages/library/user_local_tracks/local_folder.dart' as _i15; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' - as _i40; -import 'package:spotube/pages/library/user_playlists.dart' as _i41; + as _i41; +import 'package:spotube/pages/library/user_playlists.dart' as _i42; import 'package:spotube/pages/lyrics/lyrics.dart' as _i17; import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i18; -import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i42; +import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i43; import 'package:spotube/pages/player/lyrics.dart' as _i19; import 'package:spotube/pages/player/queue.dart' as _i20; import 'package:spotube/pages/player/sources.dart' as _i21; @@ -51,22 +51,23 @@ import 'package:spotube/pages/search/search.dart' as _i27; import 'package:spotube/pages/settings/about.dart' as _i1; import 'package:spotube/pages/settings/blacklist.dart' as _i4; import 'package:spotube/pages/settings/logs.dart' as _i16; -import 'package:spotube/pages/settings/settings.dart' as _i28; -import 'package:spotube/pages/stats/albums/albums.dart' as _i29; -import 'package:spotube/pages/stats/artists/artists.dart' as _i30; -import 'package:spotube/pages/stats/fees/fees.dart' as _i34; -import 'package:spotube/pages/stats/minutes/minutes.dart' as _i31; -import 'package:spotube/pages/stats/playlists/playlists.dart' as _i33; -import 'package:spotube/pages/stats/stats.dart' as _i32; -import 'package:spotube/pages/stats/streams/streams.dart' as _i35; -import 'package:spotube/pages/track/track.dart' as _i36; -import 'package:spotube/pages/webview/webview.dart' as _i43; -import 'package:spotube/services/metadata/apis/webview.dart' as _i49; +import 'package:spotube/pages/settings/metadata_plugins.dart' as _i28; +import 'package:spotube/pages/settings/settings.dart' as _i29; +import 'package:spotube/pages/stats/albums/albums.dart' as _i30; +import 'package:spotube/pages/stats/artists/artists.dart' as _i31; +import 'package:spotube/pages/stats/fees/fees.dart' as _i35; +import 'package:spotube/pages/stats/minutes/minutes.dart' as _i32; +import 'package:spotube/pages/stats/playlists/playlists.dart' as _i34; +import 'package:spotube/pages/stats/stats.dart' as _i33; +import 'package:spotube/pages/stats/streams/streams.dart' as _i36; +import 'package:spotube/pages/track/track.dart' as _i37; +import 'package:spotube/pages/webview/webview.dart' as _i44; +import 'package:spotube/services/metadata/apis/webview.dart' as _i50; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i44.PageRouteInfo { - const AboutSpotubeRoute({List<_i44.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i45.PageRouteInfo { + const AboutSpotubeRoute({List<_i45.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -74,7 +75,7 @@ class AboutSpotubeRoute extends _i44.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -84,12 +85,12 @@ class AboutSpotubeRoute extends _i44.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i44.PageRouteInfo { +class AlbumRoute extends _i45.PageRouteInfo { AlbumRoute({ - _i45.Key? key, + _i46.Key? key, required String id, - required _i46.AlbumSimple album, - List<_i44.PageRouteInfo>? children, + required _i47.AlbumSimple album, + List<_i45.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -103,7 +104,7 @@ class AlbumRoute extends _i44.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -123,11 +124,11 @@ class AlbumRouteArgs { required this.album, }); - final _i45.Key? key; + final _i46.Key? key; final String id; - final _i46.AlbumSimple album; + final _i47.AlbumSimple album; @override String toString() { @@ -137,11 +138,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i44.PageRouteInfo { +class ArtistRoute extends _i45.PageRouteInfo { ArtistRoute({ required String artistId, - _i45.Key? key, - List<_i44.PageRouteInfo>? children, + _i46.Key? key, + List<_i45.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -154,7 +155,7 @@ class ArtistRoute extends _i44.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -176,7 +177,7 @@ class ArtistRouteArgs { final String artistId; - final _i45.Key? key; + final _i46.Key? key; @override String toString() { @@ -186,8 +187,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i44.PageRouteInfo { - const BlackListRoute({List<_i44.PageRouteInfo>? children}) +class BlackListRoute extends _i45.PageRouteInfo { + const BlackListRoute({List<_i45.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -195,7 +196,7 @@ class BlackListRoute extends _i44.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -205,8 +206,8 @@ class BlackListRoute extends _i44.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i44.PageRouteInfo { - const ConnectControlRoute({List<_i44.PageRouteInfo>? children}) +class ConnectControlRoute extends _i45.PageRouteInfo { + const ConnectControlRoute({List<_i45.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -214,7 +215,7 @@ class ConnectControlRoute extends _i44.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -224,8 +225,8 @@ class ConnectControlRoute extends _i44.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i44.PageRouteInfo { - const ConnectRoute({List<_i44.PageRouteInfo>? children}) +class ConnectRoute extends _i45.PageRouteInfo { + const ConnectRoute({List<_i45.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -233,7 +234,7 @@ class ConnectRoute extends _i44.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -243,8 +244,8 @@ class ConnectRoute extends _i44.PageRouteInfo { /// generated route for /// [_i7.GenrePage] -class GenreRoute extends _i44.PageRouteInfo { - const GenreRoute({List<_i44.PageRouteInfo>? children}) +class GenreRoute extends _i45.PageRouteInfo { + const GenreRoute({List<_i45.PageRouteInfo>? children}) : super( GenreRoute.name, initialChildren: children, @@ -252,7 +253,7 @@ class GenreRoute extends _i44.PageRouteInfo { static const String name = 'GenreRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i7.GenrePage(); @@ -262,12 +263,12 @@ class GenreRoute extends _i44.PageRouteInfo { /// generated route for /// [_i8.GenrePlaylistsPage] -class GenrePlaylistsRoute extends _i44.PageRouteInfo { +class GenrePlaylistsRoute extends _i45.PageRouteInfo { GenrePlaylistsRoute({ - _i45.Key? key, + _i46.Key? key, required String id, - required _i46.Category category, - List<_i44.PageRouteInfo>? children, + required _i47.Category category, + List<_i45.PageRouteInfo>? children, }) : super( GenrePlaylistsRoute.name, args: GenrePlaylistsRouteArgs( @@ -281,7 +282,7 @@ class GenrePlaylistsRoute extends _i44.PageRouteInfo { static const String name = 'GenrePlaylistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -301,11 +302,11 @@ class GenrePlaylistsRouteArgs { required this.category, }); - final _i45.Key? key; + final _i46.Key? key; final String id; - final _i46.Category category; + final _i47.Category category; @override String toString() { @@ -315,8 +316,8 @@ class GenrePlaylistsRouteArgs { /// generated route for /// [_i9.GettingStartedPage] -class GettingStartedRoute extends _i44.PageRouteInfo { - const GettingStartedRoute({List<_i44.PageRouteInfo>? children}) +class GettingStartedRoute extends _i45.PageRouteInfo { + const GettingStartedRoute({List<_i45.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -324,7 +325,7 @@ class GettingStartedRoute extends _i44.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i9.GettingStartedPage(); @@ -335,11 +336,11 @@ class GettingStartedRoute extends _i44.PageRouteInfo { /// generated route for /// [_i10.HomeFeedSectionPage] class HomeFeedSectionRoute - extends _i44.PageRouteInfo { + extends _i45.PageRouteInfo { HomeFeedSectionRoute({ - _i47.Key? key, + _i48.Key? key, required String sectionUri, - List<_i44.PageRouteInfo>? children, + List<_i45.PageRouteInfo>? children, }) : super( HomeFeedSectionRoute.name, args: HomeFeedSectionRouteArgs( @@ -352,7 +353,7 @@ class HomeFeedSectionRoute static const String name = 'HomeFeedSectionRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -373,7 +374,7 @@ class HomeFeedSectionRouteArgs { required this.sectionUri, }); - final _i47.Key? key; + final _i48.Key? key; final String sectionUri; @@ -385,8 +386,8 @@ class HomeFeedSectionRouteArgs { /// generated route for /// [_i11.HomePage] -class HomeRoute extends _i44.PageRouteInfo { - const HomeRoute({List<_i44.PageRouteInfo>? children}) +class HomeRoute extends _i45.PageRouteInfo { + const HomeRoute({List<_i45.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -394,7 +395,7 @@ class HomeRoute extends _i44.PageRouteInfo { static const String name = 'HomeRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i11.HomePage(); @@ -404,8 +405,8 @@ class HomeRoute extends _i44.PageRouteInfo { /// generated route for /// [_i12.LastFMLoginPage] -class LastFMLoginRoute extends _i44.PageRouteInfo { - const LastFMLoginRoute({List<_i44.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i45.PageRouteInfo { + const LastFMLoginRoute({List<_i45.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -413,7 +414,7 @@ class LastFMLoginRoute extends _i44.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i12.LastFMLoginPage(); @@ -423,8 +424,8 @@ class LastFMLoginRoute extends _i44.PageRouteInfo { /// generated route for /// [_i13.LibraryPage] -class LibraryRoute extends _i44.PageRouteInfo { - const LibraryRoute({List<_i44.PageRouteInfo>? children}) +class LibraryRoute extends _i45.PageRouteInfo { + const LibraryRoute({List<_i45.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -432,7 +433,7 @@ class LibraryRoute extends _i44.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i13.LibraryPage(); @@ -442,11 +443,11 @@ class LibraryRoute extends _i44.PageRouteInfo { /// generated route for /// [_i14.LikedPlaylistPage] -class LikedPlaylistRoute extends _i44.PageRouteInfo { +class LikedPlaylistRoute extends _i45.PageRouteInfo { LikedPlaylistRoute({ - _i45.Key? key, - required _i46.PlaylistSimple playlist, - List<_i44.PageRouteInfo>? children, + _i46.Key? key, + required _i47.PlaylistSimple playlist, + List<_i45.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -458,7 +459,7 @@ class LikedPlaylistRoute extends _i44.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -476,9 +477,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i45.Key? key; + final _i46.Key? key; - final _i46.PlaylistSimple playlist; + final _i47.PlaylistSimple playlist; @override String toString() { @@ -488,13 +489,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i15.LocalLibraryPage] -class LocalLibraryRoute extends _i44.PageRouteInfo { +class LocalLibraryRoute extends _i45.PageRouteInfo { LocalLibraryRoute({ required String location, - _i45.Key? key, + _i46.Key? key, bool isDownloads = false, bool isCache = false, - List<_i44.PageRouteInfo>? children, + List<_i45.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -508,7 +509,7 @@ class LocalLibraryRoute extends _i44.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -532,7 +533,7 @@ class LocalLibraryRouteArgs { final String location; - final _i45.Key? key; + final _i46.Key? key; final bool isDownloads; @@ -546,8 +547,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i16.LogsPage] -class LogsRoute extends _i44.PageRouteInfo { - const LogsRoute({List<_i44.PageRouteInfo>? children}) +class LogsRoute extends _i45.PageRouteInfo { + const LogsRoute({List<_i45.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -555,7 +556,7 @@ class LogsRoute extends _i44.PageRouteInfo { static const String name = 'LogsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i16.LogsPage(); @@ -565,8 +566,8 @@ class LogsRoute extends _i44.PageRouteInfo { /// generated route for /// [_i17.LyricsPage] -class LyricsRoute extends _i44.PageRouteInfo { - const LyricsRoute({List<_i44.PageRouteInfo>? children}) +class LyricsRoute extends _i45.PageRouteInfo { + const LyricsRoute({List<_i45.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -574,7 +575,7 @@ class LyricsRoute extends _i44.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i17.LyricsPage(); @@ -584,11 +585,11 @@ class LyricsRoute extends _i44.PageRouteInfo { /// generated route for /// [_i18.MiniLyricsPage] -class MiniLyricsRoute extends _i44.PageRouteInfo { +class MiniLyricsRoute extends _i45.PageRouteInfo { MiniLyricsRoute({ - _i47.Key? key, - required _i47.Size prevSize, - List<_i44.PageRouteInfo>? children, + _i48.Key? key, + required _i48.Size prevSize, + List<_i45.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -600,7 +601,7 @@ class MiniLyricsRoute extends _i44.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -618,9 +619,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i47.Key? key; + final _i48.Key? key; - final _i47.Size prevSize; + final _i48.Size prevSize; @override String toString() { @@ -630,8 +631,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i19.PlayerLyricsPage] -class PlayerLyricsRoute extends _i44.PageRouteInfo { - const PlayerLyricsRoute({List<_i44.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i45.PageRouteInfo { + const PlayerLyricsRoute({List<_i45.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -639,7 +640,7 @@ class PlayerLyricsRoute extends _i44.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i19.PlayerLyricsPage(); @@ -649,8 +650,8 @@ class PlayerLyricsRoute extends _i44.PageRouteInfo { /// generated route for /// [_i20.PlayerQueuePage] -class PlayerQueueRoute extends _i44.PageRouteInfo { - const PlayerQueueRoute({List<_i44.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i45.PageRouteInfo { + const PlayerQueueRoute({List<_i45.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -658,7 +659,7 @@ class PlayerQueueRoute extends _i44.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i20.PlayerQueuePage(); @@ -668,8 +669,8 @@ class PlayerQueueRoute extends _i44.PageRouteInfo { /// generated route for /// [_i21.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i44.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i45.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i45.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -677,7 +678,7 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i21.PlayerTrackSourcesPage(); @@ -688,11 +689,11 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { /// generated route for /// [_i22.PlaylistGenerateResultPage] class PlaylistGenerateResultRoute - extends _i44.PageRouteInfo { + extends _i45.PageRouteInfo { PlaylistGenerateResultRoute({ - _i47.Key? key, - required _i48.GeneratePlaylistProviderInput state, - List<_i44.PageRouteInfo>? children, + _i48.Key? key, + required _i49.GeneratePlaylistProviderInput state, + List<_i45.PageRouteInfo>? children, }) : super( PlaylistGenerateResultRoute.name, args: PlaylistGenerateResultRouteArgs( @@ -704,7 +705,7 @@ class PlaylistGenerateResultRoute static const String name = 'PlaylistGenerateResultRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -722,9 +723,9 @@ class PlaylistGenerateResultRouteArgs { required this.state, }); - final _i47.Key? key; + final _i48.Key? key; - final _i48.GeneratePlaylistProviderInput state; + final _i49.GeneratePlaylistProviderInput state; @override String toString() { @@ -734,8 +735,8 @@ class PlaylistGenerateResultRouteArgs { /// generated route for /// [_i23.PlaylistGeneratorPage] -class PlaylistGeneratorRoute extends _i44.PageRouteInfo { - const PlaylistGeneratorRoute({List<_i44.PageRouteInfo>? children}) +class PlaylistGeneratorRoute extends _i45.PageRouteInfo { + const PlaylistGeneratorRoute({List<_i45.PageRouteInfo>? children}) : super( PlaylistGeneratorRoute.name, initialChildren: children, @@ -743,7 +744,7 @@ class PlaylistGeneratorRoute extends _i44.PageRouteInfo { static const String name = 'PlaylistGeneratorRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i23.PlaylistGeneratorPage(); @@ -753,12 +754,12 @@ class PlaylistGeneratorRoute extends _i44.PageRouteInfo { /// generated route for /// [_i24.PlaylistPage] -class PlaylistRoute extends _i44.PageRouteInfo { +class PlaylistRoute extends _i45.PageRouteInfo { PlaylistRoute({ - _i45.Key? key, + _i46.Key? key, required String id, - required _i46.PlaylistSimple playlist, - List<_i44.PageRouteInfo>? children, + required _i47.PlaylistSimple playlist, + List<_i45.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -772,7 +773,7 @@ class PlaylistRoute extends _i44.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -792,11 +793,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i45.Key? key; + final _i46.Key? key; final String id; - final _i46.PlaylistSimple playlist; + final _i47.PlaylistSimple playlist; @override String toString() { @@ -806,8 +807,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i25.ProfilePage] -class ProfileRoute extends _i44.PageRouteInfo { - const ProfileRoute({List<_i44.PageRouteInfo>? children}) +class ProfileRoute extends _i45.PageRouteInfo { + const ProfileRoute({List<_i45.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -815,7 +816,7 @@ class ProfileRoute extends _i44.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i25.ProfilePage(); @@ -825,8 +826,8 @@ class ProfileRoute extends _i44.PageRouteInfo { /// generated route for /// [_i26.RootAppPage] -class RootAppRoute extends _i44.PageRouteInfo { - const RootAppRoute({List<_i44.PageRouteInfo>? children}) +class RootAppRoute extends _i45.PageRouteInfo { + const RootAppRoute({List<_i45.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -834,7 +835,7 @@ class RootAppRoute extends _i44.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i26.RootAppPage(); @@ -844,8 +845,8 @@ class RootAppRoute extends _i44.PageRouteInfo { /// generated route for /// [_i27.SearchPage] -class SearchRoute extends _i44.PageRouteInfo { - const SearchRoute({List<_i44.PageRouteInfo>? children}) +class SearchRoute extends _i45.PageRouteInfo { + const SearchRoute({List<_i45.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -853,7 +854,7 @@ class SearchRoute extends _i44.PageRouteInfo { static const String name = 'SearchRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { return const _i27.SearchPage(); @@ -862,9 +863,28 @@ class SearchRoute extends _i44.PageRouteInfo { } /// generated route for -/// [_i28.SettingsPage] -class SettingsRoute extends _i44.PageRouteInfo { - const SettingsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i28.SettingsMetadataProviderPage] +class SettingsMetadataProviderRoute extends _i45.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i45.PageRouteInfo>? children}) + : super( + SettingsMetadataProviderRoute.name, + initialChildren: children, + ); + + static const String name = 'SettingsMetadataProviderRoute'; + + static _i45.PageInfo page = _i45.PageInfo( + name, + builder: (data) { + return const _i28.SettingsMetadataProviderPage(); + }, + ); +} + +/// generated route for +/// [_i29.SettingsPage] +class SettingsRoute extends _i45.PageRouteInfo { + const SettingsRoute({List<_i45.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -872,18 +892,18 @@ class SettingsRoute extends _i44.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i28.SettingsPage(); + return const _i29.SettingsPage(); }, ); } /// generated route for -/// [_i29.StatsAlbumsPage] -class StatsAlbumsRoute extends _i44.PageRouteInfo { - const StatsAlbumsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i30.StatsAlbumsPage] +class StatsAlbumsRoute extends _i45.PageRouteInfo { + const StatsAlbumsRoute({List<_i45.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -891,18 +911,18 @@ class StatsAlbumsRoute extends _i44.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i29.StatsAlbumsPage(); + return const _i30.StatsAlbumsPage(); }, ); } /// generated route for -/// [_i30.StatsArtistsPage] -class StatsArtistsRoute extends _i44.PageRouteInfo { - const StatsArtistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i31.StatsArtistsPage] +class StatsArtistsRoute extends _i45.PageRouteInfo { + const StatsArtistsRoute({List<_i45.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -910,18 +930,18 @@ class StatsArtistsRoute extends _i44.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i30.StatsArtistsPage(); + return const _i31.StatsArtistsPage(); }, ); } /// generated route for -/// [_i31.StatsMinutesPage] -class StatsMinutesRoute extends _i44.PageRouteInfo { - const StatsMinutesRoute({List<_i44.PageRouteInfo>? children}) +/// [_i32.StatsMinutesPage] +class StatsMinutesRoute extends _i45.PageRouteInfo { + const StatsMinutesRoute({List<_i45.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -929,18 +949,18 @@ class StatsMinutesRoute extends _i44.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i31.StatsMinutesPage(); + return const _i32.StatsMinutesPage(); }, ); } /// generated route for -/// [_i32.StatsPage] -class StatsRoute extends _i44.PageRouteInfo { - const StatsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i33.StatsPage] +class StatsRoute extends _i45.PageRouteInfo { + const StatsRoute({List<_i45.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -948,18 +968,18 @@ class StatsRoute extends _i44.PageRouteInfo { static const String name = 'StatsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i32.StatsPage(); + return const _i33.StatsPage(); }, ); } /// generated route for -/// [_i33.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i44.PageRouteInfo { - const StatsPlaylistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i34.StatsPlaylistsPage] +class StatsPlaylistsRoute extends _i45.PageRouteInfo { + const StatsPlaylistsRoute({List<_i45.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -967,18 +987,18 @@ class StatsPlaylistsRoute extends _i44.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i33.StatsPlaylistsPage(); + return const _i34.StatsPlaylistsPage(); }, ); } /// generated route for -/// [_i34.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i44.PageRouteInfo { - const StatsStreamFeesRoute({List<_i44.PageRouteInfo>? children}) +/// [_i35.StatsStreamFeesPage] +class StatsStreamFeesRoute extends _i45.PageRouteInfo { + const StatsStreamFeesRoute({List<_i45.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -986,18 +1006,18 @@ class StatsStreamFeesRoute extends _i44.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i34.StatsStreamFeesPage(); + return const _i35.StatsStreamFeesPage(); }, ); } /// generated route for -/// [_i35.StatsStreamsPage] -class StatsStreamsRoute extends _i44.PageRouteInfo { - const StatsStreamsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i36.StatsStreamsPage] +class StatsStreamsRoute extends _i45.PageRouteInfo { + const StatsStreamsRoute({List<_i45.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -1005,21 +1025,21 @@ class StatsStreamsRoute extends _i44.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i35.StatsStreamsPage(); + return const _i36.StatsStreamsPage(); }, ); } /// generated route for -/// [_i36.TrackPage] -class TrackRoute extends _i44.PageRouteInfo { +/// [_i37.TrackPage] +class TrackRoute extends _i45.PageRouteInfo { TrackRoute({ - _i47.Key? key, + _i48.Key? key, required String trackId, - List<_i44.PageRouteInfo>? children, + List<_i45.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -1032,13 +1052,13 @@ class TrackRoute extends _i44.PageRouteInfo { static const String name = 'TrackRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); - return _i36.TrackPage( + return _i37.TrackPage( key: args.key, trackId: args.trackId, ); @@ -1052,7 +1072,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i47.Key? key; + final _i48.Key? key; final String trackId; @@ -1063,9 +1083,9 @@ class TrackRouteArgs { } /// generated route for -/// [_i37.UserAlbumsPage] -class UserAlbumsRoute extends _i44.PageRouteInfo { - const UserAlbumsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i38.UserAlbumsPage] +class UserAlbumsRoute extends _i45.PageRouteInfo { + const UserAlbumsRoute({List<_i45.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -1073,18 +1093,18 @@ class UserAlbumsRoute extends _i44.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i37.UserAlbumsPage(); + return const _i38.UserAlbumsPage(); }, ); } /// generated route for -/// [_i38.UserArtistsPage] -class UserArtistsRoute extends _i44.PageRouteInfo { - const UserArtistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i39.UserArtistsPage] +class UserArtistsRoute extends _i45.PageRouteInfo { + const UserArtistsRoute({List<_i45.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -1092,18 +1112,18 @@ class UserArtistsRoute extends _i44.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i38.UserArtistsPage(); + return const _i39.UserArtistsPage(); }, ); } /// generated route for -/// [_i39.UserDownloadsPage] -class UserDownloadsRoute extends _i44.PageRouteInfo { - const UserDownloadsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i40.UserDownloadsPage] +class UserDownloadsRoute extends _i45.PageRouteInfo { + const UserDownloadsRoute({List<_i45.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -1111,18 +1131,18 @@ class UserDownloadsRoute extends _i44.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i39.UserDownloadsPage(); + return const _i40.UserDownloadsPage(); }, ); } /// generated route for -/// [_i40.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i44.PageRouteInfo { - const UserLocalLibraryRoute({List<_i44.PageRouteInfo>? children}) +/// [_i41.UserLocalLibraryPage] +class UserLocalLibraryRoute extends _i45.PageRouteInfo { + const UserLocalLibraryRoute({List<_i45.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1130,18 +1150,18 @@ class UserLocalLibraryRoute extends _i44.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i40.UserLocalLibraryPage(); + return const _i41.UserLocalLibraryPage(); }, ); } /// generated route for -/// [_i41.UserPlaylistsPage] -class UserPlaylistsRoute extends _i44.PageRouteInfo { - const UserPlaylistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i42.UserPlaylistsPage] +class UserPlaylistsRoute extends _i45.PageRouteInfo { + const UserPlaylistsRoute({List<_i45.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1149,18 +1169,18 @@ class UserPlaylistsRoute extends _i44.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i41.UserPlaylistsPage(); + return const _i42.UserPlaylistsPage(); }, ); } /// generated route for -/// [_i42.WebViewLoginPage] -class WebViewLoginRoute extends _i44.PageRouteInfo { - const WebViewLoginRoute({List<_i44.PageRouteInfo>? children}) +/// [_i43.WebViewLoginPage] +class WebViewLoginRoute extends _i45.PageRouteInfo { + const WebViewLoginRoute({List<_i45.PageRouteInfo>? children}) : super( WebViewLoginRoute.name, initialChildren: children, @@ -1168,26 +1188,26 @@ class WebViewLoginRoute extends _i44.PageRouteInfo { static const String name = 'WebViewLoginRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { - return const _i42.WebViewLoginPage(); + return const _i43.WebViewLoginPage(); }, ); } /// generated route for -/// [_i43.WebviewPage] -class WebviewRoute extends _i44.PageRouteInfo { +/// [_i44.WebviewPage] +class WebviewRoute extends _i45.PageRouteInfo { WebviewRoute({ - _i47.Key? key, - _i49.WebviewInitialSettings? initialSettings, + _i48.Key? key, + _i50.WebviewInitialSettings? initialSettings, String? url, void Function( - _i50.InAppWebViewController, - _i50.WebUri?, + _i51.InAppWebViewController, + _i51.WebUri?, )? onLoadStop, - List<_i44.PageRouteInfo>? children, + List<_i45.PageRouteInfo>? children, }) : super( WebviewRoute.name, args: WebviewRouteArgs( @@ -1201,12 +1221,12 @@ class WebviewRoute extends _i44.PageRouteInfo { static const String name = 'WebviewRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i45.PageInfo page = _i45.PageInfo( name, builder: (data) { final args = data.argsAs(orElse: () => const WebviewRouteArgs()); - return _i43.WebviewPage( + return _i44.WebviewPage( key: args.key, initialSettings: args.initialSettings, url: args.url, @@ -1224,15 +1244,15 @@ class WebviewRouteArgs { this.onLoadStop, }); - final _i47.Key? key; + final _i48.Key? key; - final _i49.WebviewInitialSettings? initialSettings; + final _i50.WebviewInitialSettings? initialSettings; final String? url; final void Function( - _i50.InAppWebViewController, - _i50.WebUri?, + _i51.InAppWebViewController, + _i51.WebUri?, )? onLoadStop; @override diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index bd9d037c..5efba917 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -135,4 +135,7 @@ abstract class SpotubeIcons { static const list = FeatherIcons.list; static const device = FeatherIcons.smartphone; static const engine = FeatherIcons.server; + static const extensions = FeatherIcons.package; + static const message = FeatherIcons.send; + static const upload = FeatherIcons.uploadCloud; } diff --git a/lib/main.dart b/lib/main.dart index f178b663..3e9edbcd 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -30,6 +30,7 @@ import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/provider/audio_player/audio_player_streams.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/glance/glance.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/server/bonsoir.dart'; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/provider/tray_manager/tray_manager.dart'; @@ -145,6 +146,8 @@ class Spotube extends HookConsumerWidget { ref.listen(audioPlayerStreamListenersProvider, (_, __) {}); ref.listen(bonsoirProvider, (_, __) {}); ref.listen(connectClientsProvider, (_, __) {}); + ref.listen(metadataPluginsProvider, (_, __) {}); + ref.listen(metadataPluginApiProvider, (_, __) {}); ref.listen(serverProvider, (_, __) {}); ref.listen(trayManagerProvider, (_, __) {}); diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 151bbc78..20aa649a 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -4,6 +4,7 @@ import 'dart:convert'; import 'dart:io'; import 'package:drift/drift.dart'; +import 'package:drift/remote.dart'; import 'package:encrypt/encrypt.dart'; import 'package:media_kit/media_kit.dart' hide Track; import 'package:path/path.dart'; @@ -35,6 +36,7 @@ part 'tables/source_match.dart'; part 'tables/audio_player_state.dart'; part 'tables/history.dart'; part 'tables/lyrics.dart'; +part 'tables/metadata_plugins.dart'; part 'typeconverters/color.dart'; part 'typeconverters/locale.dart'; @@ -56,13 +58,14 @@ part 'typeconverters/subtitle.dart'; PlaylistMediaTable, HistoryTable, LyricsTable, + MetadataPluginsTable, ], ) class AppDatabase extends _$AppDatabase { AppDatabase() : super(_openConnection()); @override - int get schemaVersion => 6; + int get schemaVersion => 7; @override MigrationStrategy get migration { @@ -115,11 +118,21 @@ class AppDatabase extends _$AppDatabase { ); }, from5To6: (m, schema) async { - // Add new column to preferences table - await m.addColumn( - schema.preferencesTable, - schema.preferencesTable.connectPort, - ); + try { + await m.addColumn( + schema.preferencesTable, + schema.preferencesTable.connectPort, + ); + } on DriftRemoteException catch (e) { + // If the column already exists, ignore the error + if (e.remoteCause != + 'duplicate column name: ${schema.preferencesTable.connectPort.name}') { + rethrow; + } + } + }, + from6To7: (m, schema) async { + await m.createTable(schema.metadataPluginsTable); }, ), ); diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 5dfe8db8..210e8d49 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -4275,6 +4275,353 @@ class LyricsTableCompanion extends UpdateCompanion { } } +class $MetadataPluginsTableTable extends MetadataPluginsTable + with TableInfo<$MetadataPluginsTableTable, MetadataPluginsTableData> { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + $MetadataPluginsTableTable(this.attachedDatabase, [this._alias]); + static const VerificationMeta _idMeta = const VerificationMeta('id'); + @override + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + static const VerificationMeta _nameMeta = const VerificationMeta('name'); + @override + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), + type: DriftSqlType.string, + requiredDuringInsert: true); + static const VerificationMeta _descriptionMeta = + const VerificationMeta('description'); + @override + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _versionMeta = + const VerificationMeta('version'); + @override + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _authorMeta = const VerificationMeta('author'); + @override + late final GeneratedColumn author = GeneratedColumn( + 'author', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _selectedMeta = + const VerificationMeta('selected'); + @override + late final GeneratedColumn selected = GeneratedColumn( + 'selected', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => + [id, name, description, version, author, selected]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'metadata_plugins_table'; + @override + VerificationContext validateIntegrity( + Insertable instance, + {bool isInserting = false}) { + final context = VerificationContext(); + final data = instance.toColumns(true); + if (data.containsKey('id')) { + context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); + } + if (data.containsKey('name')) { + context.handle( + _nameMeta, name.isAcceptableOrUnknown(data['name']!, _nameMeta)); + } else if (isInserting) { + context.missing(_nameMeta); + } + if (data.containsKey('description')) { + context.handle( + _descriptionMeta, + description.isAcceptableOrUnknown( + data['description']!, _descriptionMeta)); + } else if (isInserting) { + context.missing(_descriptionMeta); + } + if (data.containsKey('version')) { + context.handle(_versionMeta, + version.isAcceptableOrUnknown(data['version']!, _versionMeta)); + } else if (isInserting) { + context.missing(_versionMeta); + } + if (data.containsKey('author')) { + context.handle(_authorMeta, + author.isAcceptableOrUnknown(data['author']!, _authorMeta)); + } else if (isInserting) { + context.missing(_authorMeta); + } + if (data.containsKey('selected')) { + context.handle(_selectedMeta, + selected.isAcceptableOrUnknown(data['selected']!, _selectedMeta)); + } + return context; + } + + @override + Set get $primaryKey => {id}; + @override + MetadataPluginsTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MetadataPluginsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version'])!, + author: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + selected: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, + ); + } + + @override + $MetadataPluginsTableTable createAlias(String alias) { + return $MetadataPluginsTableTable(attachedDatabase, alias); + } +} + +class MetadataPluginsTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String description; + final String version; + final String author; + final bool selected; + const MetadataPluginsTableData( + {required this.id, + required this.name, + required this.description, + required this.version, + required this.author, + required this.selected}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['version'] = Variable(version); + map['author'] = Variable(author); + map['selected'] = Variable(selected); + return map; + } + + MetadataPluginsTableCompanion toCompanion(bool nullToAbsent) { + return MetadataPluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + selected: Value(selected), + ); + } + + factory MetadataPluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MetadataPluginsTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + version: serializer.fromJson(json['version']), + author: serializer.fromJson(json['author']), + selected: serializer.fromJson(json['selected']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'version': serializer.toJson(version), + 'author': serializer.toJson(author), + 'selected': serializer.toJson(selected), + }; + } + + MetadataPluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + bool? selected}) => + MetadataPluginsTableData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + selected: selected ?? this.selected, + ); + MetadataPluginsTableData copyWithCompanion( + MetadataPluginsTableCompanion data) { + return MetadataPluginsTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: + data.description.present ? data.description.value : this.description, + version: data.version.present ? data.version.value : this.version, + author: data.author.present ? data.author.value : this.author, + selected: data.selected.present ? data.selected.value : this.selected, + ); + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('selected: $selected') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, description, version, author, selected); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MetadataPluginsTableData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.version == this.version && + other.author == this.author && + other.selected == this.selected); +} + +class MetadataPluginsTableCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value version; + final Value author; + final Value selected; + const MetadataPluginsTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.version = const Value.absent(), + this.author = const Value.absent(), + this.selected = const Value.absent(), + }); + MetadataPluginsTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String description, + required String version, + required String author, + this.selected = const Value.absent(), + }) : name = Value(name), + description = Value(description), + version = Value(version), + author = Value(author); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? version, + Expression? author, + Expression? selected, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (version != null) 'version': version, + if (author != null) 'author': author, + if (selected != null) 'selected': selected, + }); + } + + MetadataPluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? selected}) { + return MetadataPluginsTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + selected: selected ?? this.selected, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (author.present) { + map['author'] = Variable(author.value); + } + if (selected.present) { + map['selected'] = Variable(selected.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('selected: $selected') + ..write(')')) + .toString(); + } +} + abstract class _$AppDatabase extends GeneratedDatabase { _$AppDatabase(QueryExecutor e) : super(e); $AppDatabaseManager get managers => $AppDatabaseManager(this); @@ -4295,6 +4642,8 @@ abstract class _$AppDatabase extends GeneratedDatabase { $PlaylistMediaTableTable(this); late final $HistoryTableTable historyTable = $HistoryTableTable(this); late final $LyricsTableTable lyricsTable = $LyricsTableTable(this); + late final $MetadataPluginsTableTable metadataPluginsTable = + $MetadataPluginsTableTable(this); late final Index uniqueBlacklist = Index('unique_blacklist', 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); late final Index uniqTrackMatch = Index('uniq_track_match', @@ -4315,6 +4664,7 @@ abstract class _$AppDatabase extends GeneratedDatabase { playlistMediaTable, historyTable, lyricsTable, + metadataPluginsTable, uniqueBlacklist, uniqTrackMatch ]; @@ -6909,6 +7259,194 @@ typedef $$LyricsTableTableProcessedTableManager = ProcessedTableManager< ), LyricsTableData, PrefetchHooks Function()>; +typedef $$MetadataPluginsTableTableCreateCompanionBuilder + = MetadataPluginsTableCompanion Function({ + Value id, + required String name, + required String description, + required String version, + required String author, + Value selected, +}); +typedef $$MetadataPluginsTableTableUpdateCompanionBuilder + = MetadataPluginsTableCompanion Function({ + Value id, + Value name, + Value description, + Value version, + Value author, + Value selected, +}); + +class $$MetadataPluginsTableTableFilterComposer + extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { + $$MetadataPluginsTableTableFilterComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnFilters get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnFilters(column)); + + ColumnFilters get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnFilters(column)); + + ColumnFilters get description => $composableBuilder( + column: $table.description, builder: (column) => ColumnFilters(column)); + + ColumnFilters get version => $composableBuilder( + column: $table.version, builder: (column) => ColumnFilters(column)); + + ColumnFilters get author => $composableBuilder( + column: $table.author, builder: (column) => ColumnFilters(column)); + + ColumnFilters get selected => $composableBuilder( + column: $table.selected, builder: (column) => ColumnFilters(column)); +} + +class $$MetadataPluginsTableTableOrderingComposer + extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { + $$MetadataPluginsTableTableOrderingComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + ColumnOrderings get id => $composableBuilder( + column: $table.id, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get name => $composableBuilder( + column: $table.name, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get description => $composableBuilder( + column: $table.description, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get version => $composableBuilder( + column: $table.version, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get author => $composableBuilder( + column: $table.author, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get selected => $composableBuilder( + column: $table.selected, builder: (column) => ColumnOrderings(column)); +} + +class $$MetadataPluginsTableTableAnnotationComposer + extends Composer<_$AppDatabase, $MetadataPluginsTableTable> { + $$MetadataPluginsTableTableAnnotationComposer({ + required super.$db, + required super.$table, + super.joinBuilder, + super.$addJoinBuilderToRootComposer, + super.$removeJoinBuilderFromRootComposer, + }); + GeneratedColumn get id => + $composableBuilder(column: $table.id, builder: (column) => column); + + GeneratedColumn get name => + $composableBuilder(column: $table.name, builder: (column) => column); + + GeneratedColumn get description => $composableBuilder( + column: $table.description, builder: (column) => column); + + GeneratedColumn get version => + $composableBuilder(column: $table.version, builder: (column) => column); + + GeneratedColumn get author => + $composableBuilder(column: $table.author, builder: (column) => column); + + GeneratedColumn get selected => + $composableBuilder(column: $table.selected, builder: (column) => column); +} + +class $$MetadataPluginsTableTableTableManager extends RootTableManager< + _$AppDatabase, + $MetadataPluginsTableTable, + MetadataPluginsTableData, + $$MetadataPluginsTableTableFilterComposer, + $$MetadataPluginsTableTableOrderingComposer, + $$MetadataPluginsTableTableAnnotationComposer, + $$MetadataPluginsTableTableCreateCompanionBuilder, + $$MetadataPluginsTableTableUpdateCompanionBuilder, + ( + MetadataPluginsTableData, + BaseReferences<_$AppDatabase, $MetadataPluginsTableTable, + MetadataPluginsTableData> + ), + MetadataPluginsTableData, + PrefetchHooks Function()> { + $$MetadataPluginsTableTableTableManager( + _$AppDatabase db, $MetadataPluginsTableTable table) + : super(TableManagerState( + db: db, + table: table, + createFilteringComposer: () => + $$MetadataPluginsTableTableFilterComposer($db: db, $table: table), + createOrderingComposer: () => + $$MetadataPluginsTableTableOrderingComposer( + $db: db, $table: table), + createComputedFieldComposer: () => + $$MetadataPluginsTableTableAnnotationComposer( + $db: db, $table: table), + updateCompanionCallback: ({ + Value id = const Value.absent(), + Value name = const Value.absent(), + Value description = const Value.absent(), + Value version = const Value.absent(), + Value author = const Value.absent(), + Value selected = const Value.absent(), + }) => + MetadataPluginsTableCompanion( + id: id, + name: name, + description: description, + version: version, + author: author, + selected: selected, + ), + createCompanionCallback: ({ + Value id = const Value.absent(), + required String name, + required String description, + required String version, + required String author, + Value selected = const Value.absent(), + }) => + MetadataPluginsTableCompanion.insert( + id: id, + name: name, + description: description, + version: version, + author: author, + selected: selected, + ), + withReferenceMapper: (p0) => p0 + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) + .toList(), + prefetchHooksCallback: null, + )); +} + +typedef $$MetadataPluginsTableTableProcessedTableManager + = ProcessedTableManager< + _$AppDatabase, + $MetadataPluginsTableTable, + MetadataPluginsTableData, + $$MetadataPluginsTableTableFilterComposer, + $$MetadataPluginsTableTableOrderingComposer, + $$MetadataPluginsTableTableAnnotationComposer, + $$MetadataPluginsTableTableCreateCompanionBuilder, + $$MetadataPluginsTableTableUpdateCompanionBuilder, + ( + MetadataPluginsTableData, + BaseReferences<_$AppDatabase, $MetadataPluginsTableTable, + MetadataPluginsTableData> + ), + MetadataPluginsTableData, + PrefetchHooks Function()>; class $AppDatabaseManager { final _$AppDatabase _db; @@ -6935,4 +7473,6 @@ class $AppDatabaseManager { $$HistoryTableTableTableManager(_db, _db.historyTable); $$LyricsTableTableTableManager get lyricsTable => $$LyricsTableTableTableManager(_db, _db.lyricsTable); + $$MetadataPluginsTableTableTableManager get metadataPluginsTable => + $$MetadataPluginsTableTableTableManager(_db, _db.metadataPluginsTable); } diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index 3e416ea5..51873c60 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1692,12 +1692,285 @@ class Shape13 extends i0.VersionedTable { i1.GeneratedColumn _column_56(String aliasedName) => i1.GeneratedColumn('connect_port', aliasedName, false, type: i1.DriftSqlType.int, defaultValue: const Constant(-1)); + +final class Schema7 extends i0.VersionedSchema { + Schema7({required super.database}) : super(version: 7); + @override + late final List entities = [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + playlistTable, + playlistMediaTable, + historyTable, + lyricsTable, + metadataPluginsTable, + uniqueBlacklist, + uniqTrackMatch, + ]; + late final Shape0 authenticationTable = Shape0( + source: i0.VersionedTable( + entityName: 'authentication_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_1, + _column_2, + _column_3, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape1 blacklistTable = Shape1( + source: i0.VersionedTable( + entityName: 'blacklist_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_4, + _column_5, + _column_6, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape13 preferencesTable = Shape13( + source: i0.VersionedTable( + entityName: 'preferences_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_7, + _column_8, + _column_9, + _column_10, + _column_11, + _column_12, + _column_13, + _column_14, + _column_15, + _column_55, + _column_17, + _column_18, + _column_19, + _column_20, + _column_21, + _column_22, + _column_23, + _column_24, + _column_25, + _column_26, + _column_54, + _column_27, + _column_28, + _column_29, + _column_30, + _column_31, + _column_56, + _column_53, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape3 scrobblerTable = Shape3( + source: i0.VersionedTable( + entityName: 'scrobbler_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_32, + _column_33, + _column_34, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape4 skipSegmentTable = Shape4( + source: i0.VersionedTable( + entityName: 'skip_segment_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_35, + _column_36, + _column_37, + _column_32, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape5 sourceMatchTable = Shape5( + source: i0.VersionedTable( + entityName: 'source_match_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_37, + _column_38, + _column_39, + _column_32, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape6 audioPlayerStateTable = Shape6( + source: i0.VersionedTable( + entityName: 'audio_player_state_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_40, + _column_41, + _column_42, + _column_43, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape7 playlistTable = Shape7( + source: i0.VersionedTable( + entityName: 'playlist_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_44, + _column_45, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape8 playlistMediaTable = Shape8( + source: i0.VersionedTable( + entityName: 'playlist_media_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_46, + _column_47, + _column_48, + _column_49, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape9 historyTable = Shape9( + source: i0.VersionedTable( + entityName: 'history_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_32, + _column_50, + _column_51, + _column_52, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape10 lyricsTable = Shape10( + source: i0.VersionedTable( + entityName: 'lyrics_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_37, + _column_52, + ], + attachedDatabase: database, + ), + alias: null); + late final Shape14 metadataPluginsTable = Shape14( + source: i0.VersionedTable( + entityName: 'metadata_plugins_table', + withoutRowId: false, + isStrict: false, + tableConstraints: [], + columns: [ + _column_0, + _column_57, + _column_58, + _column_59, + _column_60, + _column_61, + ], + attachedDatabase: database, + ), + alias: null); + final i1.Index uniqueBlacklist = i1.Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); + final i1.Index uniqTrackMatch = i1.Index('uniq_track_match', + 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)'); +} + +class Shape14 extends i0.VersionedTable { + Shape14({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get name => + columnsByName['name']! as i1.GeneratedColumn; + i1.GeneratedColumn get description => + columnsByName['description']! as i1.GeneratedColumn; + i1.GeneratedColumn get version => + columnsByName['version']! as i1.GeneratedColumn; + i1.GeneratedColumn get author => + columnsByName['author']! as i1.GeneratedColumn; + i1.GeneratedColumn get selected => + columnsByName['selected']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_57(String aliasedName) => + i1.GeneratedColumn('name', aliasedName, false, + additionalChecks: i1.GeneratedColumn.checkTextLength( + minTextLength: 1, maxTextLength: 50), + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_58(String aliasedName) => + i1.GeneratedColumn('description', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_59(String aliasedName) => + i1.GeneratedColumn('version', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_60(String aliasedName) => + i1.GeneratedColumn('author', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_61(String aliasedName) => + i1.GeneratedColumn('selected', aliasedName, false, + type: i1.DriftSqlType.bool, + defaultConstraints: i1.GeneratedColumn.constraintIsAlways( + 'CHECK ("selected" IN (0, 1))'), + defaultValue: const Constant(false)); i0.MigrationStepWithVersion migrationSteps({ required Future Function(i1.Migrator m, Schema2 schema) from1To2, required Future Function(i1.Migrator m, Schema3 schema) from2To3, required Future Function(i1.Migrator m, Schema4 schema) from3To4, required Future Function(i1.Migrator m, Schema5 schema) from4To5, required Future Function(i1.Migrator m, Schema6 schema) from5To6, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, }) { return (currentVersion, database) async { switch (currentVersion) { @@ -1726,6 +1999,11 @@ i0.MigrationStepWithVersion migrationSteps({ final migrator = i1.Migrator(database, schema); await from5To6(migrator, schema); return 6; + case 6: + final schema = Schema7(database: database); + final migrator = i1.Migrator(database, schema); + await from6To7(migrator, schema); + return 7; default: throw ArgumentError.value('Unknown migration from $currentVersion'); } @@ -1738,6 +2016,7 @@ i1.OnUpgrade stepByStep({ required Future Function(i1.Migrator m, Schema4 schema) from3To4, required Future Function(i1.Migrator m, Schema5 schema) from4To5, required Future Function(i1.Migrator m, Schema6 schema) from5To6, + required Future Function(i1.Migrator m, Schema7 schema) from6To7, }) => i0.VersionedSchema.stepByStepHelper( step: migrationSteps( @@ -1746,4 +2025,5 @@ i1.OnUpgrade stepByStep({ from3To4: from3To4, from4To5: from4To5, from5To6: from5To6, + from6To7: from6To7, )); diff --git a/lib/models/database/tables/metadata_plugins.dart b/lib/models/database/tables/metadata_plugins.dart new file mode 100644 index 00000000..e2f49a92 --- /dev/null +++ b/lib/models/database/tables/metadata_plugins.dart @@ -0,0 +1,10 @@ +part of '../database.dart'; + +class MetadataPluginsTable extends Table { + IntColumn get id => integer().autoIncrement()(); + TextColumn get name => text().withLength(min: 1, max: 50)(); + TextColumn get description => text()(); + TextColumn get version => text()(); + TextColumn get author => text()(); + BoolColumn get selected => boolean().withDefault(const Constant(false))(); +} diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart new file mode 100644 index 00000000..35ea5fdd --- /dev/null +++ b/lib/pages/settings/metadata_plugins.dart @@ -0,0 +1,182 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/components/form/text_form_field.dart'; +import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:file_picker/file_picker.dart'; + +@RoutePage() +class SettingsMetadataProviderPage extends HookConsumerWidget { + const SettingsMetadataProviderPage({super.key}); + + @override + Widget build(BuildContext context, ref) { + final formKey = useMemoized(() => GlobalKey(), []); + + final plugins = ref.watch(metadataPluginsProvider); + final pluginsNotifier = ref.watch(metadataPluginsProvider.notifier); + final metadataApi = ref.watch(metadataPluginApiProvider); + final isAuthenticated = ref.watch(metadataAuthenticatedProvider); + + final artists = ref.watch(metadataUserArtistsProvider); + + return Scaffold( + headers: const [ + TitleBar( + title: Text("Metadata provider plugin"), + ) + ], + child: Padding( + padding: const EdgeInsets.all(8), + child: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Row( + spacing: 8, + children: [ + Expanded( + child: FormBuilder( + key: formKey, + child: TextFormBuilderField( + name: "plugin_url", + validator: FormBuilderValidators.url( + protocols: ["http", "https"]), + placeholder: const Text( + "Add GitHub/Codeberg URL to plugin repository " + "or direct link to .smplug file", + ), + ), + ), + ), + Tooltip( + tooltip: const TooltipContainer( + child: Text("Download and install plugin from url"), + ).call, + child: IconButton.secondary( + icon: const Icon(SpotubeIcons.download), + onPressed: () async { + if (formKey.currentState?.saveAndValidate() ?? false) { + final url = formKey.currentState?.fields["plugin_url"] + ?.value as String; + + if (url.isNotEmpty) { + final pluginConfig = await pluginsNotifier + .downloadAndCachePlugin(url); + + await pluginsNotifier.addPlugin(pluginConfig); + } + } + }, + ), + ), + Tooltip( + tooltip: const TooltipContainer( + child: Text("Upload plugin from file"), + ).call, + child: IconButton.primary( + icon: const Icon(SpotubeIcons.upload), + onPressed: () async { + final result = await FilePicker.platform.pickFiles( + type: FileType.custom, + allowedExtensions: ["smplug"], + withData: true, + ); + + if (result == null) return; + + final file = result.files.first; + + if (file.bytes == null) return; + + final pluginConfig = await pluginsNotifier + .extractPluginArchive(file.bytes!); + await pluginsNotifier.addPlugin(pluginConfig); + }, + ), + ), + ], + ), + ), + const SliverGap(20), + SliverList.separated( + itemCount: plugins.asData?.value.plugins.length ?? 0, + separatorBuilder: (context, index) => const Divider(), + itemBuilder: (context, index) { + final plugin = plugins.asData!.value.plugins[index]; + final isDefault = plugins.asData!.value.defaultPlugin == index; + final requiresAuth = isDefault && + metadataApi.hasValue && + metadataApi.asData?.value?.signatureFlags.requiresAuth == + true; + return Card( + child: Column( + spacing: 8, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Basic( + title: Text(plugin.name), + subtitle: Text(plugin.description), + trailing: Row( + spacing: 8, + children: [ + Button.primary( + enabled: !isDefault, + onPressed: () async { + await pluginsNotifier.setDefaultPlugin(plugin); + }, + child: isDefault + ? const Text("Default") + : const Text("Make default"), + ), + IconButton.destructive( + onPressed: () async { + await pluginsNotifier.removePlugin(plugin); + }, + icon: const Icon(SpotubeIcons.trash), + ), + ], + ), + ), + if (requiresAuth) + Row( + children: [ + const Text("Plugin requires authentication"), + const Spacer(), + if (isAuthenticated.asData?.value != true) + Button.primary( + onPressed: () async { + await metadataApi.asData?.value + ?.authenticate(); + }, + leading: const Icon(SpotubeIcons.login), + child: const Text("Login"), + ) + else + Button.destructive( + onPressed: () async { + await metadataApi.asData?.value?.logout(); + }, + leading: const Icon(SpotubeIcons.logout), + child: const Text("Logout"), + ), + ], + ) + ], + ), + ); + }, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/settings/sections/accounts.dart b/lib/pages/settings/sections/accounts.dart index a381dc23..c9051470 100644 --- a/lib/pages/settings/sections/accounts.dart +++ b/lib/pages/settings/sections/accounts.dart @@ -1,5 +1,4 @@ import 'package:auto_route/auto_route.dart'; -import 'package:auto_size_text/auto_size_text.dart'; import 'package:flutter/material.dart' show ListTile; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -7,103 +6,29 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/pages/mobile_login/hooks/login_callback.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; class SettingsAccountSection extends HookConsumerWidget { const SettingsAccountSection({super.key}); @override Widget build(context, ref) { - final theme = Theme.of(context); - - final auth = ref.watch(authenticationProvider); final scrobbler = ref.watch(scrobblerProvider); - final me = ref.watch(meProvider); - final meData = me.asData?.value; - - final onLogin = useLoginCallback(ref); return SectionCardWithHeading( heading: context.l10n.account, children: [ - if (auth.asData?.value != null) - ListTile( - leading: const Icon(SpotubeIcons.user), - title: Text(context.l10n.user_profile), - trailing: Padding( - padding: const EdgeInsets.all(8.0), - child: Avatar( - initials: Avatar.getInitials(meData?.displayName ?? "User"), - provider: UniversalImage.imageProvider( - (meData?.images).asUrlString( - placeholder: ImagePlaceholder.artist, - ), - ), - ), - ), - onTap: () { - context.navigateTo(const ProfileRoute()); - }, - ), - if (auth.asData?.value == null) - LayoutBuilder(builder: (context, constrains) { - return ListTile( - leading: Icon( - SpotubeIcons.spotify, - color: theme.colorScheme.primary, - ), - title: Align( - alignment: Alignment.centerLeft, - child: AutoSizeText( - context.l10n.login_with_spotify, - maxLines: 1, - style: TextStyle( - color: theme.colorScheme.primary, - ), - ), - ), - onTap: constrains.mdAndUp ? null : onLogin, - trailing: constrains.smAndDown - ? null - : Button.primary( - onPressed: onLogin, - child: Text( - context.l10n.connect_with_spotify.toUpperCase(), - ), - ), - ); - }) - else - Builder(builder: (context) { - return ListTile( - leading: const Icon(SpotubeIcons.spotify), - title: SizedBox( - height: 50, - width: 180, - child: Align( - alignment: Alignment.centerLeft, - child: AutoSizeText( - context.l10n.logout_of_this_account, - maxLines: 1, - ), - ), - ), - trailing: Button.destructive( - onPressed: () async { - ref.read(authenticationProvider.notifier).logout(); - context.maybePop(); - }, - child: Text(context.l10n.logout), - ), - ); - }), + ListTile( + leading: const Icon(SpotubeIcons.extensions), + title: const Text("Metadata provider plugins"), + subtitle: const Text( + "Configure your own playlist/album/artist/feed metadata provider"), + onTap: () { + context.pushRoute(const SettingsMetadataProviderRoute()); + }, + trailing: const Icon(SpotubeIcons.angleRight), + ), if (scrobbler.asData?.value == null) ListTile( leading: const Icon(SpotubeIcons.lastFm), diff --git a/lib/provider/metadata_plugin/auth.dart b/lib/provider/metadata_plugin/auth.dart new file mode 100644 index 00000000..0ccc9cd4 --- /dev/null +++ b/lib/provider/metadata_plugin/auth.dart @@ -0,0 +1,49 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; + +class MetadataAuthenticationNotifier extends AsyncNotifier { + MetadataAuthenticationNotifier(); + @override + build() async { + final metadataApi = await ref.watch(metadataPluginApiProvider.future); + + if (metadataApi?.signatureFlags.requiresAuth != true) { + return false; + } + + final subscription = metadataApi?.authenticatedStream.listen((event) { + state = AsyncValue.data(event); + }); + + ref.onDispose(() { + subscription?.cancel(); + }); + + return await metadataApi?.isAuthenticated() ?? false; + } + + Future login() async { + final metadataApi = await ref.read(metadataPluginApiProvider.future); + + if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) { + return; + } + + await metadataApi.authenticate(); + } + + Future logout() async { + final metadataApi = await ref.read(metadataPluginApiProvider.future); + + if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) { + return; + } + + await metadataApi.logout(); + } +} + +final metadataAuthenticatedProvider = + AsyncNotifierProvider( + () => MetadataAuthenticationNotifier(), +); diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart new file mode 100644 index 00000000..3e6197e6 --- /dev/null +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -0,0 +1,365 @@ +import 'dart:convert'; +import 'dart:io'; + +import 'package:collection/collection.dart'; +import 'package:dio/dio.dart'; +import 'package:drift/drift.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:path/path.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/services/dio/dio.dart'; +import 'package:spotube/services/metadata/metadata.dart'; +import 'package:spotube/utils/service_utils.dart'; +import 'package:uuid/uuid.dart'; +import 'package:archive/archive.dart'; + +final allowedDomainsRegex = RegExp( + r"^(https?:\/\/)?(www\.)?(github\.com|codeberg\.org)\/.+", +); + +class MetadataPluginState { + final List plugins; + final int defaultPlugin; + + const MetadataPluginState({ + this.plugins = const [], + this.defaultPlugin = -1, + }); + + PluginConfiguration? get defaultPluginConfig { + if (defaultPlugin < 0 || defaultPlugin >= plugins.length) { + return null; + } + return plugins[defaultPlugin]; + } + + factory MetadataPluginState.fromJson(Map json) { + return MetadataPluginState( + plugins: (json["plugins"] as List) + .map((e) => PluginConfiguration.fromJson(e)) + .toList(), + defaultPlugin: json["default_plugin"] ?? -1, + ); + } + + Map toJson() { + return { + "plugins": plugins.map((e) => e.toJson()).toList(), + "default_plugin": defaultPlugin, + }; + } + + MetadataPluginState copyWith({ + List? plugins, + int? defaultPlugin, + }) { + return MetadataPluginState( + plugins: plugins ?? this.plugins, + defaultPlugin: defaultPlugin ?? this.defaultPlugin, + ); + } +} + +class MetadataPluginNotifier extends AsyncNotifier { + AppDatabase get database => ref.read(databaseProvider); + + @override + build() async { + final database = ref.watch(databaseProvider); + + final subscription = database.metadataPluginsTable.select().watch().listen( + (event) async { + state = AsyncValue.data(await toStatePlugins(event)); + }, + ); + + ref.onDispose(() { + subscription.cancel(); + }); + + final plugins = await database.metadataPluginsTable.select().get(); + + return await toStatePlugins(plugins); + } + + Future toStatePlugins( + List plugins) async { + int defaultPlugin = -1; + final pluginConfigs = plugins.mapIndexed( + (index, plugin) { + if (plugin.selected) { + defaultPlugin = index; + } + + return PluginConfiguration( + type: PluginType.metadata, + name: plugin.name, + author: plugin.author, + description: plugin.description, + version: plugin.version, + ); + }, + ).toList(); + return MetadataPluginState( + plugins: pluginConfigs, + defaultPlugin: defaultPlugin, + ); + } + + Uri _getGithubReleasesUrl(String repoUrl) { + final parsedUri = Uri.parse(repoUrl); + final uri = parsedUri.replace( + host: "api.github.com", + path: "/repos/${parsedUri.path}/releases", + queryParameters: { + "per_page": "1", + "page": "1", + }, + ); + + return uri; + } + + Uri _getCodebergeReleasesUrl(String repoUrl) { + final parsedUri = Uri.parse(repoUrl); + final uri = parsedUri.replace( + path: "/api/v1/repos/${parsedUri.path}/releases", + queryParameters: { + "limit": "1", + "page": "1", + }, + ); + + return uri; + } + + Future _getPluginDownloadUrl(Uri uri) async { + final res = await globalDio.getUri( + uri, + options: Options(responseType: ResponseType.json), + ); + + if (res.statusCode != 200) { + throw Exception("Failed to get releases"); + } + final releases = res.data as List; + if (releases.isEmpty) { + throw Exception("No releases found"); + } + final latestRelease = releases.first; + final downloadUrl = (latestRelease["assets"] as List).firstWhere( + (asset) => (asset["name"] as String).endsWith(".smplug"), + )["browser_download_url"]; + if (downloadUrl == null) { + throw Exception("No download URL found"); + } + return downloadUrl; + } + + Future _getPluginDir() async => Directory( + join( + (await getApplicationCacheDirectory()).path, + "metadata-plugins", + ), + ); + + Future extractPluginArchive(List bytes) async { + final archive = ZipDecoder().decodeBytes(bytes); + final pluginJson = archive + .firstWhereOrNull((file) => file.isFile && file.name == "plugin.json"); + + if (pluginJson == null) { + throw Exception("No plugin.json found"); + } + final pluginConfig = PluginConfiguration.fromJson( + jsonDecode( + utf8.decode(pluginJson.content as List), + ) as Map, + ); + + final pluginDir = await _getPluginDir(); + await pluginDir.create(recursive: true); + + final pluginExtractionDirPath = join( + pluginDir.path, + ServiceUtils.sanitizeFilename(pluginConfig.name), + ); + + for (final file in archive) { + if (file.isFile) { + final filename = file.name; + final data = file.content as List; + final extractedFile = File(join( + pluginExtractionDirPath, + filename, + )); + await extractedFile.create(recursive: true); + await extractedFile.writeAsBytes(data); + } + } + + return pluginConfig; + } + + /// Downloads, extracts & caches the plugin from the given URL and returns the plugin config. + /// If only a text/html URL is provided, it will try to get the latest release from + /// the URL for supported websites (github.com, codeberg.org). + Future downloadAndCachePlugin(String url) async { + final res = await globalDio.head(url); + final isSupportedWebsite = + (res.headers["Content-Type"] as String?)?.startsWith("text/html") == + true && + allowedDomainsRegex.hasMatch(url); + String pluginDownloadUrl = url; + if (isSupportedWebsite) { + if (url.contains("github.com")) { + final uri = _getGithubReleasesUrl(url); + pluginDownloadUrl = await _getPluginDownloadUrl(uri); + } else if (url.contains("codeberg.org")) { + final uri = _getCodebergeReleasesUrl(url); + pluginDownloadUrl = await _getPluginDownloadUrl(uri); + } else { + throw Exception("Unsupported website"); + } + } + + // Now let's download, extract and cache the plugin + final pluginDir = await _getPluginDir(); + await pluginDir.create(recursive: true); + + final tempPluginName = "${const Uuid().v4()}.smplug"; + final pluginFile = File(join(pluginDir.path, tempPluginName)); + + final pluginRes = await globalDio.download( + pluginDownloadUrl, + pluginFile.path, + options: Options( + responseType: ResponseType.bytes, + followRedirects: true, + receiveTimeout: const Duration(seconds: 30), + ), + ); + + if ((pluginRes.statusCode ?? 500) > 299) { + throw Exception("Failed to download plugin"); + } + + return await extractPluginArchive(await pluginFile.readAsBytes()); + } + + Future addPlugin(PluginConfiguration plugin) async { + final pluginRes = await (database.metadataPluginsTable.select() + ..where( + (tbl) => tbl.name.equals(plugin.name), + ) + ..limit(1)) + .get(); + + if (pluginRes.isNotEmpty) { + throw Exception("Plugin already exists"); + } + + await database.metadataPluginsTable.insertOne( + MetadataPluginsTableCompanion.insert( + name: plugin.name, + author: plugin.author, + description: plugin.description, + version: plugin.version, + ), + ); + } + + Future removePlugin(PluginConfiguration plugin) async { + final pluginDir = await _getPluginDir(); + final pluginExtractionDirPath = join( + pluginDir.path, + ServiceUtils.sanitizeFilename(plugin.name), + ); + final pluginExtractionDir = Directory(pluginExtractionDirPath); + if (pluginExtractionDir.existsSync()) { + await pluginExtractionDir.delete(recursive: true); + } + await database.metadataPluginsTable + .deleteWhere((tbl) => tbl.name.equals(plugin.name)); + } + + Future setDefaultPlugin(PluginConfiguration plugin) async { + await (database.metadataPluginsTable.update() + ..where((tbl) => tbl.name.equals(plugin.name))) + .write( + const MetadataPluginsTableCompanion(selected: Value(true)), + ); + } + + Future getPluginLibraryCode(PluginConfiguration plugin) async { + final pluginDir = await _getPluginDir(); + final pluginExtractionDirPath = join( + pluginDir.path, + ServiceUtils.sanitizeFilename(plugin.name), + ); + + final libraryFile = File(join(pluginExtractionDirPath, "dist", "index.js")); + + if (!libraryFile.existsSync()) { + throw Exception("No dist/index.js found"); + } + + return await libraryFile.readAsString(); + } +} + +final metadataPluginsProvider = + AsyncNotifierProvider( + MetadataPluginNotifier.new, +); + +final metadataPluginApiProvider = FutureProvider( + (ref) async { + final defaultPlugin = await ref.watch( + metadataPluginsProvider.selectAsync((data) => data.defaultPluginConfig), + ); + + if (defaultPlugin == null) { + return null; + } + + final pluginsNotifier = ref.read(metadataPluginsProvider.notifier); + final libraryCode = + await pluginsNotifier.getPluginLibraryCode(defaultPlugin); + + return MetadataApiSignature.init(libraryCode, defaultPlugin); + }, +); + +final metadataProviderUserProvider = FutureProvider( + (ref) async { + final metadataApi = await ref.watch(metadataPluginApiProvider.future); + ref.watch(metadataAuthenticatedProvider); + + if (metadataApi == null) { + return null; + } + return metadataApi.getMe(); + }, +); + +final metadataUserArtistsProvider = + FutureProvider>((ref) async { + final metadataApi = await ref.watch(metadataPluginApiProvider.future); + ref.watch(metadataAuthenticatedProvider); + + final userId = await ref.watch( + metadataProviderUserProvider.selectAsync((data) => data?.uid), + ); + if (metadataApi == null || userId == null) { + return []; + } + + final res = await metadataApi.listUserSavedArtists(userId); + + return res.items as List; +}); diff --git a/lib/services/metadata/apis/set_interval.dart b/lib/services/metadata/apis/set_interval.dart index 1b1aba97..f059dc75 100644 --- a/lib/services/metadata/apis/set_interval.dart +++ b/lib/services/metadata/apis/set_interval.dart @@ -66,7 +66,7 @@ class PluginSetIntervalApi { } on Exception catch (e) { print('Exception no clearInterval: $e'); } on Error catch (e) { - print('Erro no clearInterval: $e'); + print('Error no clearInterval: $e'); } }); } diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 57a38bc2..b01e41be 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -38,6 +38,10 @@ class MetadataApiSignature { final PluginSetIntervalApi setIntervalApi; late MetadataSignatureFlags _signatureFlags; + final StreamController _authenticatedStreamController; + + Stream get authenticatedStream => _authenticatedStreamController.stream; + MetadataSignatureFlags get signatureFlags => _signatureFlags; MetadataApiSignature._( @@ -46,10 +50,19 @@ class MetadataApiSignature { this.webViewApi, this.totpGenerator, this.setIntervalApi, - ); + ) : _authenticatedStreamController = StreamController.broadcast() { + runtime.onMessage("authenticatedStatus", (args) { + if (args[0] is Map && (args[0] as Map).containsKey("authenticated")) { + final authenticated = args[0]["authenticated"] as bool; + _authenticatedStreamController.add(authenticated); + } + }); + } static Future init( - String libraryCode, PluginConfiguration config) async { + String libraryCode, + PluginConfiguration config, + ) async { final runtime = getJavascriptRuntime(xhr: true).enableXhr(); runtime.enableHandlePromises(); await runtime.enableFetch(); @@ -106,6 +119,7 @@ class MetadataApiSignature { Future invoke(String method, [List? args]) async { final completer = Completer(); runtime.onMessage(method, (result) { + if (completer.isCompleted) return; try { if (result is Map && result.containsKey("error")) { completer.completeError(result["error"]); @@ -113,7 +127,10 @@ class MetadataApiSignature { completer.complete(result is String ? jsonDecode(result) : result); } } catch (e, stack) { - AppLogger.reportError(e, stack); + AppLogger.reportError( + "[MetadataApiSignature][invoke] Error in $method: $e", + stack, + ); } }); final code = """ @@ -157,6 +174,11 @@ class MetadataApiSignature { await invoke("metadataApi.authenticate"); } + Future isAuthenticated() async { + final res = await invoke("metadataApi.isAuthenticated"); + return res as bool; + } + Future logout() async { await invoke("metadataApi.logout"); } diff --git a/pubspec.lock b/pubspec.lock index 76e05a60..dcb307a9 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -71,7 +71,7 @@ packages: source: hosted version: "1.0.4" archive: - dependency: transitive + dependency: "direct main" description: name: archive sha256: "2fde1607386ab523f7a36bb3e7edb43bd58e6edaf2ffb29d8a6d578b297fdbbd" diff --git a/pubspec.yaml b/pubspec.yaml index be622bf1..6c7c1d8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -143,6 +143,7 @@ dependencies: otp_util: ^1.0.2 dio_http2_adapter: ^2.6.0 flutter_js: ^0.8.2 + archive: ^4.0.7 dev_dependencies: build_runner: ^2.4.13 diff --git a/test/drift/app_db/generated/schema.dart b/test/drift/app_db/generated/schema.dart index 2ab213a2..fb6abdf4 100644 --- a/test/drift/app_db/generated/schema.dart +++ b/test/drift/app_db/generated/schema.dart @@ -8,6 +8,7 @@ import 'schema_v5.dart' as v5; import 'schema_v6.dart' as v6; import 'schema_v1.dart' as v1; import 'schema_v2.dart' as v2; +import 'schema_v7.dart' as v7; import 'schema_v4.dart' as v4; class GeneratedHelper implements SchemaInstantiationHelper { @@ -24,6 +25,8 @@ class GeneratedHelper implements SchemaInstantiationHelper { return v1.DatabaseAtV1(db); case 2: return v2.DatabaseAtV2(db); + case 7: + return v7.DatabaseAtV7(db); case 4: return v4.DatabaseAtV4(db); default: @@ -31,5 +34,5 @@ class GeneratedHelper implements SchemaInstantiationHelper { } } - static const versions = const [1, 2, 3, 4, 5, 6]; + static const versions = const [1, 2, 3, 4, 5, 6, 7]; } diff --git a/test/drift/app_db/generated/schema_v7.dart b/test/drift/app_db/generated/schema_v7.dart new file mode 100644 index 00000000..dcd2e2a9 --- /dev/null +++ b/test/drift/app_db/generated/schema_v7.dart @@ -0,0 +1,3758 @@ +// dart format width=80 +// GENERATED CODE, DO NOT EDIT BY HAND. +// ignore_for_file: type=lint +import 'package:drift/drift.dart'; + +class AuthenticationTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AuthenticationTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn cookie = GeneratedColumn( + 'cookie', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn accessToken = GeneratedColumn( + 'access_token', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn expiration = GeneratedColumn( + 'expiration', aliasedName, false, + type: DriftSqlType.dateTime, requiredDuringInsert: true); + @override + List get $columns => [id, cookie, accessToken, expiration]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'authentication_table'; + @override + Set get $primaryKey => {id}; + @override + AuthenticationTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AuthenticationTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + cookie: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}cookie'])!, + accessToken: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}access_token'])!, + expiration: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}expiration'])!, + ); + } + + @override + AuthenticationTable createAlias(String alias) { + return AuthenticationTable(attachedDatabase, alias); + } +} + +class AuthenticationTableData extends DataClass + implements Insertable { + final int id; + final String cookie; + final String accessToken; + final DateTime expiration; + const AuthenticationTableData( + {required this.id, + required this.cookie, + required this.accessToken, + required this.expiration}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['cookie'] = Variable(cookie); + map['access_token'] = Variable(accessToken); + map['expiration'] = Variable(expiration); + return map; + } + + AuthenticationTableCompanion toCompanion(bool nullToAbsent) { + return AuthenticationTableCompanion( + id: Value(id), + cookie: Value(cookie), + accessToken: Value(accessToken), + expiration: Value(expiration), + ); + } + + factory AuthenticationTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AuthenticationTableData( + id: serializer.fromJson(json['id']), + cookie: serializer.fromJson(json['cookie']), + accessToken: serializer.fromJson(json['accessToken']), + expiration: serializer.fromJson(json['expiration']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'cookie': serializer.toJson(cookie), + 'accessToken': serializer.toJson(accessToken), + 'expiration': serializer.toJson(expiration), + }; + } + + AuthenticationTableData copyWith( + {int? id, + String? cookie, + String? accessToken, + DateTime? expiration}) => + AuthenticationTableData( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + AuthenticationTableData copyWithCompanion(AuthenticationTableCompanion data) { + return AuthenticationTableData( + id: data.id.present ? data.id.value : this.id, + cookie: data.cookie.present ? data.cookie.value : this.cookie, + accessToken: + data.accessToken.present ? data.accessToken.value : this.accessToken, + expiration: + data.expiration.present ? data.expiration.value : this.expiration, + ); + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableData(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, cookie, accessToken, expiration); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AuthenticationTableData && + other.id == this.id && + other.cookie == this.cookie && + other.accessToken == this.accessToken && + other.expiration == this.expiration); +} + +class AuthenticationTableCompanion + extends UpdateCompanion { + final Value id; + final Value cookie; + final Value accessToken; + final Value expiration; + const AuthenticationTableCompanion({ + this.id = const Value.absent(), + this.cookie = const Value.absent(), + this.accessToken = const Value.absent(), + this.expiration = const Value.absent(), + }); + AuthenticationTableCompanion.insert({ + this.id = const Value.absent(), + required String cookie, + required String accessToken, + required DateTime expiration, + }) : cookie = Value(cookie), + accessToken = Value(accessToken), + expiration = Value(expiration); + static Insertable custom({ + Expression? id, + Expression? cookie, + Expression? accessToken, + Expression? expiration, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (cookie != null) 'cookie': cookie, + if (accessToken != null) 'access_token': accessToken, + if (expiration != null) 'expiration': expiration, + }); + } + + AuthenticationTableCompanion copyWith( + {Value? id, + Value? cookie, + Value? accessToken, + Value? expiration}) { + return AuthenticationTableCompanion( + id: id ?? this.id, + cookie: cookie ?? this.cookie, + accessToken: accessToken ?? this.accessToken, + expiration: expiration ?? this.expiration, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (cookie.present) { + map['cookie'] = Variable(cookie.value); + } + if (accessToken.present) { + map['access_token'] = Variable(accessToken.value); + } + if (expiration.present) { + map['expiration'] = Variable(expiration.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AuthenticationTableCompanion(') + ..write('id: $id, ') + ..write('cookie: $cookie, ') + ..write('accessToken: $accessToken, ') + ..write('expiration: $expiration') + ..write(')')) + .toString(); + } +} + +class BlacklistTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + BlacklistTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementType = GeneratedColumn( + 'element_type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn elementId = GeneratedColumn( + 'element_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, name, elementType, elementId]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'blacklist_table'; + @override + Set get $primaryKey => {id}; + @override + BlacklistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return BlacklistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + elementType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_type'])!, + elementId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}element_id'])!, + ); + } + + @override + BlacklistTable createAlias(String alias) { + return BlacklistTable(attachedDatabase, alias); + } +} + +class BlacklistTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String elementType; + final String elementId; + const BlacklistTableData( + {required this.id, + required this.name, + required this.elementType, + required this.elementId}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['element_type'] = Variable(elementType); + map['element_id'] = Variable(elementId); + return map; + } + + BlacklistTableCompanion toCompanion(bool nullToAbsent) { + return BlacklistTableCompanion( + id: Value(id), + name: Value(name), + elementType: Value(elementType), + elementId: Value(elementId), + ); + } + + factory BlacklistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return BlacklistTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + elementType: serializer.fromJson(json['elementType']), + elementId: serializer.fromJson(json['elementId']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'elementType': serializer.toJson(elementType), + 'elementId': serializer.toJson(elementId), + }; + } + + BlacklistTableData copyWith( + {int? id, String? name, String? elementType, String? elementId}) => + BlacklistTableData( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + BlacklistTableData copyWithCompanion(BlacklistTableCompanion data) { + return BlacklistTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + elementType: + data.elementType.present ? data.elementType.value : this.elementType, + elementId: data.elementId.present ? data.elementId.value : this.elementId, + ); + } + + @override + String toString() { + return (StringBuffer('BlacklistTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, name, elementType, elementId); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is BlacklistTableData && + other.id == this.id && + other.name == this.name && + other.elementType == this.elementType && + other.elementId == this.elementId); +} + +class BlacklistTableCompanion extends UpdateCompanion { + final Value id; + final Value name; + final Value elementType; + final Value elementId; + const BlacklistTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.elementType = const Value.absent(), + this.elementId = const Value.absent(), + }); + BlacklistTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String elementType, + required String elementId, + }) : name = Value(name), + elementType = Value(elementType), + elementId = Value(elementId); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? elementType, + Expression? elementId, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (elementType != null) 'element_type': elementType, + if (elementId != null) 'element_id': elementId, + }); + } + + BlacklistTableCompanion copyWith( + {Value? id, + Value? name, + Value? elementType, + Value? elementId}) { + return BlacklistTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + elementType: elementType ?? this.elementType, + elementId: elementId ?? this.elementId, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (elementType.present) { + map['element_type'] = Variable(elementType.value); + } + if (elementId.present) { + map['element_id'] = Variable(elementId.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('BlacklistTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('elementType: $elementType, ') + ..write('elementId: $elementId') + ..write(')')) + .toString(); + } +} + +class PreferencesTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PreferencesTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn audioQuality = GeneratedColumn( + 'audio_quality', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceQualities.high.name)); + late final GeneratedColumn albumColorSync = GeneratedColumn( + 'album_color_sync', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("album_color_sync" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn amoledDarkTheme = GeneratedColumn( + 'amoled_dark_theme', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("amoled_dark_theme" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn checkUpdate = GeneratedColumn( + 'check_update', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("check_update" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn normalizeAudio = GeneratedColumn( + 'normalize_audio', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("normalize_audio" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn showSystemTrayIcon = GeneratedColumn( + 'show_system_tray_icon', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("show_system_tray_icon" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn systemTitleBar = GeneratedColumn( + 'system_title_bar', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("system_title_bar" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn skipNonMusic = GeneratedColumn( + 'skip_non_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("skip_non_music" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn closeBehavior = GeneratedColumn( + 'close_behavior', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(CloseBehavior.close.name)); + late final GeneratedColumn accentColorScheme = + GeneratedColumn('accent_color_scheme', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("Orange:0xFFf97315")); + late final GeneratedColumn layoutMode = GeneratedColumn( + 'layout_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(LayoutMode.adaptive.name)); + late final GeneratedColumn locale = GeneratedColumn( + 'locale', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: + const Constant('{"languageCode":"system","countryCode":"system"}')); + late final GeneratedColumn market = GeneratedColumn( + 'market', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(Market.US.name)); + late final GeneratedColumn searchMode = GeneratedColumn( + 'search_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SearchMode.youtube.name)); + late final GeneratedColumn downloadLocation = GeneratedColumn( + 'download_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn localLibraryLocation = + GeneratedColumn('local_library_location', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("")); + late final GeneratedColumn pipedInstance = GeneratedColumn( + 'piped_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://pipedapi.kavin.rocks")); + late final GeneratedColumn invidiousInstance = + GeneratedColumn('invidious_instance', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("https://inv.nadeko.net")); + late final GeneratedColumn themeMode = GeneratedColumn( + 'theme_mode', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(ThemeMode.system.name)); + late final GeneratedColumn audioSource = GeneratedColumn( + 'audio_source', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(AudioSource.youtube.name)); + late final GeneratedColumn youtubeClientEngine = + GeneratedColumn('youtube_client_engine', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name)); + late final GeneratedColumn streamMusicCodec = GeneratedColumn( + 'stream_music_codec', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceCodecs.weba.name)); + late final GeneratedColumn downloadMusicCodec = + GeneratedColumn('download_music_codec', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceCodecs.m4a.name)); + late final GeneratedColumn discordPresence = GeneratedColumn( + 'discord_presence', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("discord_presence" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn endlessPlayback = GeneratedColumn( + 'endless_playback', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("endless_playback" IN (0, 1))'), + defaultValue: const Constant(true)); + late final GeneratedColumn enableConnect = GeneratedColumn( + 'enable_connect', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'CHECK ("enable_connect" IN (0, 1))'), + defaultValue: const Constant(false)); + late final GeneratedColumn connectPort = GeneratedColumn( + 'connect_port', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(-1)); + late final GeneratedColumn cacheMusic = GeneratedColumn( + 'cache_music', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("cache_music" IN (0, 1))'), + defaultValue: const Constant(true)); + @override + List get $columns => [ + id, + audioQuality, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSource, + youtubeClientEngine, + streamMusicCodec, + downloadMusicCodec, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'preferences_table'; + @override + Set get $primaryKey => {id}; + @override + PreferencesTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PreferencesTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + audioQuality: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_quality'])!, + albumColorSync: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}album_color_sync'])!, + amoledDarkTheme: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}amoled_dark_theme'])!, + checkUpdate: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}check_update'])!, + normalizeAudio: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}normalize_audio'])!, + showSystemTrayIcon: attachedDatabase.typeMapping.read( + DriftSqlType.bool, data['${effectivePrefix}show_system_tray_icon'])!, + systemTitleBar: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}system_title_bar'])!, + skipNonMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}skip_non_music'])!, + closeBehavior: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}close_behavior'])!, + accentColorScheme: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}accent_color_scheme'])!, + layoutMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}layout_mode'])!, + locale: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}locale'])!, + market: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}market'])!, + searchMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}search_mode'])!, + downloadLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_location'])!, + localLibraryLocation: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}local_library_location'])!, + pipedInstance: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}piped_instance'])!, + invidiousInstance: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}invidious_instance'])!, + themeMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}theme_mode'])!, + audioSource: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}audio_source'])!, + youtubeClientEngine: attachedDatabase.typeMapping.read( + DriftSqlType.string, + data['${effectivePrefix}youtube_client_engine'])!, + streamMusicCodec: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}stream_music_codec'])!, + downloadMusicCodec: attachedDatabase.typeMapping.read( + DriftSqlType.string, data['${effectivePrefix}download_music_codec'])!, + discordPresence: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}discord_presence'])!, + endlessPlayback: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}endless_playback'])!, + enableConnect: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}enable_connect'])!, + connectPort: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}connect_port'])!, + cacheMusic: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}cache_music'])!, + ); + } + + @override + PreferencesTable createAlias(String alias) { + return PreferencesTable(attachedDatabase, alias); + } +} + +class PreferencesTableData extends DataClass + implements Insertable { + final int id; + final String audioQuality; + final bool albumColorSync; + final bool amoledDarkTheme; + final bool checkUpdate; + final bool normalizeAudio; + final bool showSystemTrayIcon; + final bool systemTitleBar; + final bool skipNonMusic; + final String closeBehavior; + final String accentColorScheme; + final String layoutMode; + final String locale; + final String market; + final String searchMode; + final String downloadLocation; + final String localLibraryLocation; + final String pipedInstance; + final String invidiousInstance; + final String themeMode; + final String audioSource; + final String youtubeClientEngine; + final String streamMusicCodec; + final String downloadMusicCodec; + final bool discordPresence; + final bool endlessPlayback; + final bool enableConnect; + final int connectPort; + final bool cacheMusic; + const PreferencesTableData( + {required this.id, + required this.audioQuality, + required this.albumColorSync, + required this.amoledDarkTheme, + required this.checkUpdate, + required this.normalizeAudio, + required this.showSystemTrayIcon, + required this.systemTitleBar, + required this.skipNonMusic, + required this.closeBehavior, + required this.accentColorScheme, + required this.layoutMode, + required this.locale, + required this.market, + required this.searchMode, + required this.downloadLocation, + required this.localLibraryLocation, + required this.pipedInstance, + required this.invidiousInstance, + required this.themeMode, + required this.audioSource, + required this.youtubeClientEngine, + required this.streamMusicCodec, + required this.downloadMusicCodec, + required this.discordPresence, + required this.endlessPlayback, + required this.enableConnect, + required this.connectPort, + required this.cacheMusic}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['audio_quality'] = Variable(audioQuality); + map['album_color_sync'] = Variable(albumColorSync); + map['amoled_dark_theme'] = Variable(amoledDarkTheme); + map['check_update'] = Variable(checkUpdate); + map['normalize_audio'] = Variable(normalizeAudio); + map['show_system_tray_icon'] = Variable(showSystemTrayIcon); + map['system_title_bar'] = Variable(systemTitleBar); + map['skip_non_music'] = Variable(skipNonMusic); + map['close_behavior'] = Variable(closeBehavior); + map['accent_color_scheme'] = Variable(accentColorScheme); + map['layout_mode'] = Variable(layoutMode); + map['locale'] = Variable(locale); + map['market'] = Variable(market); + map['search_mode'] = Variable(searchMode); + map['download_location'] = Variable(downloadLocation); + map['local_library_location'] = Variable(localLibraryLocation); + map['piped_instance'] = Variable(pipedInstance); + map['invidious_instance'] = Variable(invidiousInstance); + map['theme_mode'] = Variable(themeMode); + map['audio_source'] = Variable(audioSource); + map['youtube_client_engine'] = Variable(youtubeClientEngine); + map['stream_music_codec'] = Variable(streamMusicCodec); + map['download_music_codec'] = Variable(downloadMusicCodec); + map['discord_presence'] = Variable(discordPresence); + map['endless_playback'] = Variable(endlessPlayback); + map['enable_connect'] = Variable(enableConnect); + map['connect_port'] = Variable(connectPort); + map['cache_music'] = Variable(cacheMusic); + return map; + } + + PreferencesTableCompanion toCompanion(bool nullToAbsent) { + return PreferencesTableCompanion( + id: Value(id), + audioQuality: Value(audioQuality), + albumColorSync: Value(albumColorSync), + amoledDarkTheme: Value(amoledDarkTheme), + checkUpdate: Value(checkUpdate), + normalizeAudio: Value(normalizeAudio), + showSystemTrayIcon: Value(showSystemTrayIcon), + systemTitleBar: Value(systemTitleBar), + skipNonMusic: Value(skipNonMusic), + closeBehavior: Value(closeBehavior), + accentColorScheme: Value(accentColorScheme), + layoutMode: Value(layoutMode), + locale: Value(locale), + market: Value(market), + searchMode: Value(searchMode), + downloadLocation: Value(downloadLocation), + localLibraryLocation: Value(localLibraryLocation), + pipedInstance: Value(pipedInstance), + invidiousInstance: Value(invidiousInstance), + themeMode: Value(themeMode), + audioSource: Value(audioSource), + youtubeClientEngine: Value(youtubeClientEngine), + streamMusicCodec: Value(streamMusicCodec), + downloadMusicCodec: Value(downloadMusicCodec), + discordPresence: Value(discordPresence), + endlessPlayback: Value(endlessPlayback), + enableConnect: Value(enableConnect), + connectPort: Value(connectPort), + cacheMusic: Value(cacheMusic), + ); + } + + factory PreferencesTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PreferencesTableData( + id: serializer.fromJson(json['id']), + audioQuality: serializer.fromJson(json['audioQuality']), + albumColorSync: serializer.fromJson(json['albumColorSync']), + amoledDarkTheme: serializer.fromJson(json['amoledDarkTheme']), + checkUpdate: serializer.fromJson(json['checkUpdate']), + normalizeAudio: serializer.fromJson(json['normalizeAudio']), + showSystemTrayIcon: serializer.fromJson(json['showSystemTrayIcon']), + systemTitleBar: serializer.fromJson(json['systemTitleBar']), + skipNonMusic: serializer.fromJson(json['skipNonMusic']), + closeBehavior: serializer.fromJson(json['closeBehavior']), + accentColorScheme: serializer.fromJson(json['accentColorScheme']), + layoutMode: serializer.fromJson(json['layoutMode']), + locale: serializer.fromJson(json['locale']), + market: serializer.fromJson(json['market']), + searchMode: serializer.fromJson(json['searchMode']), + downloadLocation: serializer.fromJson(json['downloadLocation']), + localLibraryLocation: + serializer.fromJson(json['localLibraryLocation']), + pipedInstance: serializer.fromJson(json['pipedInstance']), + invidiousInstance: serializer.fromJson(json['invidiousInstance']), + themeMode: serializer.fromJson(json['themeMode']), + audioSource: serializer.fromJson(json['audioSource']), + youtubeClientEngine: + serializer.fromJson(json['youtubeClientEngine']), + streamMusicCodec: serializer.fromJson(json['streamMusicCodec']), + downloadMusicCodec: + serializer.fromJson(json['downloadMusicCodec']), + discordPresence: serializer.fromJson(json['discordPresence']), + endlessPlayback: serializer.fromJson(json['endlessPlayback']), + enableConnect: serializer.fromJson(json['enableConnect']), + connectPort: serializer.fromJson(json['connectPort']), + cacheMusic: serializer.fromJson(json['cacheMusic']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'audioQuality': serializer.toJson(audioQuality), + 'albumColorSync': serializer.toJson(albumColorSync), + 'amoledDarkTheme': serializer.toJson(amoledDarkTheme), + 'checkUpdate': serializer.toJson(checkUpdate), + 'normalizeAudio': serializer.toJson(normalizeAudio), + 'showSystemTrayIcon': serializer.toJson(showSystemTrayIcon), + 'systemTitleBar': serializer.toJson(systemTitleBar), + 'skipNonMusic': serializer.toJson(skipNonMusic), + 'closeBehavior': serializer.toJson(closeBehavior), + 'accentColorScheme': serializer.toJson(accentColorScheme), + 'layoutMode': serializer.toJson(layoutMode), + 'locale': serializer.toJson(locale), + 'market': serializer.toJson(market), + 'searchMode': serializer.toJson(searchMode), + 'downloadLocation': serializer.toJson(downloadLocation), + 'localLibraryLocation': serializer.toJson(localLibraryLocation), + 'pipedInstance': serializer.toJson(pipedInstance), + 'invidiousInstance': serializer.toJson(invidiousInstance), + 'themeMode': serializer.toJson(themeMode), + 'audioSource': serializer.toJson(audioSource), + 'youtubeClientEngine': serializer.toJson(youtubeClientEngine), + 'streamMusicCodec': serializer.toJson(streamMusicCodec), + 'downloadMusicCodec': serializer.toJson(downloadMusicCodec), + 'discordPresence': serializer.toJson(discordPresence), + 'endlessPlayback': serializer.toJson(endlessPlayback), + 'enableConnect': serializer.toJson(enableConnect), + 'connectPort': serializer.toJson(connectPort), + 'cacheMusic': serializer.toJson(cacheMusic), + }; + } + + PreferencesTableData copyWith( + {int? id, + String? audioQuality, + bool? albumColorSync, + bool? amoledDarkTheme, + bool? checkUpdate, + bool? normalizeAudio, + bool? showSystemTrayIcon, + bool? systemTitleBar, + bool? skipNonMusic, + String? closeBehavior, + String? accentColorScheme, + String? layoutMode, + String? locale, + String? market, + String? searchMode, + String? downloadLocation, + String? localLibraryLocation, + String? pipedInstance, + String? invidiousInstance, + String? themeMode, + String? audioSource, + String? youtubeClientEngine, + String? streamMusicCodec, + String? downloadMusicCodec, + bool? discordPresence, + bool? endlessPlayback, + bool? enableConnect, + int? connectPort, + bool? cacheMusic}) => + PreferencesTableData( + id: id ?? this.id, + audioQuality: audioQuality ?? this.audioQuality, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSource: audioSource ?? this.audioSource, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, + downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + PreferencesTableData copyWithCompanion(PreferencesTableCompanion data) { + return PreferencesTableData( + id: data.id.present ? data.id.value : this.id, + audioQuality: data.audioQuality.present + ? data.audioQuality.value + : this.audioQuality, + albumColorSync: data.albumColorSync.present + ? data.albumColorSync.value + : this.albumColorSync, + amoledDarkTheme: data.amoledDarkTheme.present + ? data.amoledDarkTheme.value + : this.amoledDarkTheme, + checkUpdate: + data.checkUpdate.present ? data.checkUpdate.value : this.checkUpdate, + normalizeAudio: data.normalizeAudio.present + ? data.normalizeAudio.value + : this.normalizeAudio, + showSystemTrayIcon: data.showSystemTrayIcon.present + ? data.showSystemTrayIcon.value + : this.showSystemTrayIcon, + systemTitleBar: data.systemTitleBar.present + ? data.systemTitleBar.value + : this.systemTitleBar, + skipNonMusic: data.skipNonMusic.present + ? data.skipNonMusic.value + : this.skipNonMusic, + closeBehavior: data.closeBehavior.present + ? data.closeBehavior.value + : this.closeBehavior, + accentColorScheme: data.accentColorScheme.present + ? data.accentColorScheme.value + : this.accentColorScheme, + layoutMode: + data.layoutMode.present ? data.layoutMode.value : this.layoutMode, + locale: data.locale.present ? data.locale.value : this.locale, + market: data.market.present ? data.market.value : this.market, + searchMode: + data.searchMode.present ? data.searchMode.value : this.searchMode, + downloadLocation: data.downloadLocation.present + ? data.downloadLocation.value + : this.downloadLocation, + localLibraryLocation: data.localLibraryLocation.present + ? data.localLibraryLocation.value + : this.localLibraryLocation, + pipedInstance: data.pipedInstance.present + ? data.pipedInstance.value + : this.pipedInstance, + invidiousInstance: data.invidiousInstance.present + ? data.invidiousInstance.value + : this.invidiousInstance, + themeMode: data.themeMode.present ? data.themeMode.value : this.themeMode, + audioSource: + data.audioSource.present ? data.audioSource.value : this.audioSource, + youtubeClientEngine: data.youtubeClientEngine.present + ? data.youtubeClientEngine.value + : this.youtubeClientEngine, + streamMusicCodec: data.streamMusicCodec.present + ? data.streamMusicCodec.value + : this.streamMusicCodec, + downloadMusicCodec: data.downloadMusicCodec.present + ? data.downloadMusicCodec.value + : this.downloadMusicCodec, + discordPresence: data.discordPresence.present + ? data.discordPresence.value + : this.discordPresence, + endlessPlayback: data.endlessPlayback.present + ? data.endlessPlayback.value + : this.endlessPlayback, + enableConnect: data.enableConnect.present + ? data.enableConnect.value + : this.enableConnect, + connectPort: + data.connectPort.present ? data.connectPort.value : this.connectPort, + cacheMusic: + data.cacheMusic.present ? data.cacheMusic.value : this.cacheMusic, + ); + } + + @override + String toString() { + return (StringBuffer('PreferencesTableData(') + ..write('id: $id, ') + ..write('audioQuality: $audioQuality, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSource: $audioSource, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('streamMusicCodec: $streamMusicCodec, ') + ..write('downloadMusicCodec: $downloadMusicCodec, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hashAll([ + id, + audioQuality, + albumColorSync, + amoledDarkTheme, + checkUpdate, + normalizeAudio, + showSystemTrayIcon, + systemTitleBar, + skipNonMusic, + closeBehavior, + accentColorScheme, + layoutMode, + locale, + market, + searchMode, + downloadLocation, + localLibraryLocation, + pipedInstance, + invidiousInstance, + themeMode, + audioSource, + youtubeClientEngine, + streamMusicCodec, + downloadMusicCodec, + discordPresence, + endlessPlayback, + enableConnect, + connectPort, + cacheMusic + ]); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PreferencesTableData && + other.id == this.id && + other.audioQuality == this.audioQuality && + other.albumColorSync == this.albumColorSync && + other.amoledDarkTheme == this.amoledDarkTheme && + other.checkUpdate == this.checkUpdate && + other.normalizeAudio == this.normalizeAudio && + other.showSystemTrayIcon == this.showSystemTrayIcon && + other.systemTitleBar == this.systemTitleBar && + other.skipNonMusic == this.skipNonMusic && + other.closeBehavior == this.closeBehavior && + other.accentColorScheme == this.accentColorScheme && + other.layoutMode == this.layoutMode && + other.locale == this.locale && + other.market == this.market && + other.searchMode == this.searchMode && + other.downloadLocation == this.downloadLocation && + other.localLibraryLocation == this.localLibraryLocation && + other.pipedInstance == this.pipedInstance && + other.invidiousInstance == this.invidiousInstance && + other.themeMode == this.themeMode && + other.audioSource == this.audioSource && + other.youtubeClientEngine == this.youtubeClientEngine && + other.streamMusicCodec == this.streamMusicCodec && + other.downloadMusicCodec == this.downloadMusicCodec && + other.discordPresence == this.discordPresence && + other.endlessPlayback == this.endlessPlayback && + other.enableConnect == this.enableConnect && + other.connectPort == this.connectPort && + other.cacheMusic == this.cacheMusic); +} + +class PreferencesTableCompanion extends UpdateCompanion { + final Value id; + final Value audioQuality; + final Value albumColorSync; + final Value amoledDarkTheme; + final Value checkUpdate; + final Value normalizeAudio; + final Value showSystemTrayIcon; + final Value systemTitleBar; + final Value skipNonMusic; + final Value closeBehavior; + final Value accentColorScheme; + final Value layoutMode; + final Value locale; + final Value market; + final Value searchMode; + final Value downloadLocation; + final Value localLibraryLocation; + final Value pipedInstance; + final Value invidiousInstance; + final Value themeMode; + final Value audioSource; + final Value youtubeClientEngine; + final Value streamMusicCodec; + final Value downloadMusicCodec; + final Value discordPresence; + final Value endlessPlayback; + final Value enableConnect; + final Value connectPort; + final Value cacheMusic; + const PreferencesTableCompanion({ + this.id = const Value.absent(), + this.audioQuality = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSource = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.streamMusicCodec = const Value.absent(), + this.downloadMusicCodec = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + PreferencesTableCompanion.insert({ + this.id = const Value.absent(), + this.audioQuality = const Value.absent(), + this.albumColorSync = const Value.absent(), + this.amoledDarkTheme = const Value.absent(), + this.checkUpdate = const Value.absent(), + this.normalizeAudio = const Value.absent(), + this.showSystemTrayIcon = const Value.absent(), + this.systemTitleBar = const Value.absent(), + this.skipNonMusic = const Value.absent(), + this.closeBehavior = const Value.absent(), + this.accentColorScheme = const Value.absent(), + this.layoutMode = const Value.absent(), + this.locale = const Value.absent(), + this.market = const Value.absent(), + this.searchMode = const Value.absent(), + this.downloadLocation = const Value.absent(), + this.localLibraryLocation = const Value.absent(), + this.pipedInstance = const Value.absent(), + this.invidiousInstance = const Value.absent(), + this.themeMode = const Value.absent(), + this.audioSource = const Value.absent(), + this.youtubeClientEngine = const Value.absent(), + this.streamMusicCodec = const Value.absent(), + this.downloadMusicCodec = const Value.absent(), + this.discordPresence = const Value.absent(), + this.endlessPlayback = const Value.absent(), + this.enableConnect = const Value.absent(), + this.connectPort = const Value.absent(), + this.cacheMusic = const Value.absent(), + }); + static Insertable custom({ + Expression? id, + Expression? audioQuality, + Expression? albumColorSync, + Expression? amoledDarkTheme, + Expression? checkUpdate, + Expression? normalizeAudio, + Expression? showSystemTrayIcon, + Expression? systemTitleBar, + Expression? skipNonMusic, + Expression? closeBehavior, + Expression? accentColorScheme, + Expression? layoutMode, + Expression? locale, + Expression? market, + Expression? searchMode, + Expression? downloadLocation, + Expression? localLibraryLocation, + Expression? pipedInstance, + Expression? invidiousInstance, + Expression? themeMode, + Expression? audioSource, + Expression? youtubeClientEngine, + Expression? streamMusicCodec, + Expression? downloadMusicCodec, + Expression? discordPresence, + Expression? endlessPlayback, + Expression? enableConnect, + Expression? connectPort, + Expression? cacheMusic, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (audioQuality != null) 'audio_quality': audioQuality, + if (albumColorSync != null) 'album_color_sync': albumColorSync, + if (amoledDarkTheme != null) 'amoled_dark_theme': amoledDarkTheme, + if (checkUpdate != null) 'check_update': checkUpdate, + if (normalizeAudio != null) 'normalize_audio': normalizeAudio, + if (showSystemTrayIcon != null) + 'show_system_tray_icon': showSystemTrayIcon, + if (systemTitleBar != null) 'system_title_bar': systemTitleBar, + if (skipNonMusic != null) 'skip_non_music': skipNonMusic, + if (closeBehavior != null) 'close_behavior': closeBehavior, + if (accentColorScheme != null) 'accent_color_scheme': accentColorScheme, + if (layoutMode != null) 'layout_mode': layoutMode, + if (locale != null) 'locale': locale, + if (market != null) 'market': market, + if (searchMode != null) 'search_mode': searchMode, + if (downloadLocation != null) 'download_location': downloadLocation, + if (localLibraryLocation != null) + 'local_library_location': localLibraryLocation, + if (pipedInstance != null) 'piped_instance': pipedInstance, + if (invidiousInstance != null) 'invidious_instance': invidiousInstance, + if (themeMode != null) 'theme_mode': themeMode, + if (audioSource != null) 'audio_source': audioSource, + if (youtubeClientEngine != null) + 'youtube_client_engine': youtubeClientEngine, + if (streamMusicCodec != null) 'stream_music_codec': streamMusicCodec, + if (downloadMusicCodec != null) + 'download_music_codec': downloadMusicCodec, + if (discordPresence != null) 'discord_presence': discordPresence, + if (endlessPlayback != null) 'endless_playback': endlessPlayback, + if (enableConnect != null) 'enable_connect': enableConnect, + if (connectPort != null) 'connect_port': connectPort, + if (cacheMusic != null) 'cache_music': cacheMusic, + }); + } + + PreferencesTableCompanion copyWith( + {Value? id, + Value? audioQuality, + Value? albumColorSync, + Value? amoledDarkTheme, + Value? checkUpdate, + Value? normalizeAudio, + Value? showSystemTrayIcon, + Value? systemTitleBar, + Value? skipNonMusic, + Value? closeBehavior, + Value? accentColorScheme, + Value? layoutMode, + Value? locale, + Value? market, + Value? searchMode, + Value? downloadLocation, + Value? localLibraryLocation, + Value? pipedInstance, + Value? invidiousInstance, + Value? themeMode, + Value? audioSource, + Value? youtubeClientEngine, + Value? streamMusicCodec, + Value? downloadMusicCodec, + Value? discordPresence, + Value? endlessPlayback, + Value? enableConnect, + Value? connectPort, + Value? cacheMusic}) { + return PreferencesTableCompanion( + id: id ?? this.id, + audioQuality: audioQuality ?? this.audioQuality, + albumColorSync: albumColorSync ?? this.albumColorSync, + amoledDarkTheme: amoledDarkTheme ?? this.amoledDarkTheme, + checkUpdate: checkUpdate ?? this.checkUpdate, + normalizeAudio: normalizeAudio ?? this.normalizeAudio, + showSystemTrayIcon: showSystemTrayIcon ?? this.showSystemTrayIcon, + systemTitleBar: systemTitleBar ?? this.systemTitleBar, + skipNonMusic: skipNonMusic ?? this.skipNonMusic, + closeBehavior: closeBehavior ?? this.closeBehavior, + accentColorScheme: accentColorScheme ?? this.accentColorScheme, + layoutMode: layoutMode ?? this.layoutMode, + locale: locale ?? this.locale, + market: market ?? this.market, + searchMode: searchMode ?? this.searchMode, + downloadLocation: downloadLocation ?? this.downloadLocation, + localLibraryLocation: localLibraryLocation ?? this.localLibraryLocation, + pipedInstance: pipedInstance ?? this.pipedInstance, + invidiousInstance: invidiousInstance ?? this.invidiousInstance, + themeMode: themeMode ?? this.themeMode, + audioSource: audioSource ?? this.audioSource, + youtubeClientEngine: youtubeClientEngine ?? this.youtubeClientEngine, + streamMusicCodec: streamMusicCodec ?? this.streamMusicCodec, + downloadMusicCodec: downloadMusicCodec ?? this.downloadMusicCodec, + discordPresence: discordPresence ?? this.discordPresence, + endlessPlayback: endlessPlayback ?? this.endlessPlayback, + enableConnect: enableConnect ?? this.enableConnect, + connectPort: connectPort ?? this.connectPort, + cacheMusic: cacheMusic ?? this.cacheMusic, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (audioQuality.present) { + map['audio_quality'] = Variable(audioQuality.value); + } + if (albumColorSync.present) { + map['album_color_sync'] = Variable(albumColorSync.value); + } + if (amoledDarkTheme.present) { + map['amoled_dark_theme'] = Variable(amoledDarkTheme.value); + } + if (checkUpdate.present) { + map['check_update'] = Variable(checkUpdate.value); + } + if (normalizeAudio.present) { + map['normalize_audio'] = Variable(normalizeAudio.value); + } + if (showSystemTrayIcon.present) { + map['show_system_tray_icon'] = Variable(showSystemTrayIcon.value); + } + if (systemTitleBar.present) { + map['system_title_bar'] = Variable(systemTitleBar.value); + } + if (skipNonMusic.present) { + map['skip_non_music'] = Variable(skipNonMusic.value); + } + if (closeBehavior.present) { + map['close_behavior'] = Variable(closeBehavior.value); + } + if (accentColorScheme.present) { + map['accent_color_scheme'] = Variable(accentColorScheme.value); + } + if (layoutMode.present) { + map['layout_mode'] = Variable(layoutMode.value); + } + if (locale.present) { + map['locale'] = Variable(locale.value); + } + if (market.present) { + map['market'] = Variable(market.value); + } + if (searchMode.present) { + map['search_mode'] = Variable(searchMode.value); + } + if (downloadLocation.present) { + map['download_location'] = Variable(downloadLocation.value); + } + if (localLibraryLocation.present) { + map['local_library_location'] = + Variable(localLibraryLocation.value); + } + if (pipedInstance.present) { + map['piped_instance'] = Variable(pipedInstance.value); + } + if (invidiousInstance.present) { + map['invidious_instance'] = Variable(invidiousInstance.value); + } + if (themeMode.present) { + map['theme_mode'] = Variable(themeMode.value); + } + if (audioSource.present) { + map['audio_source'] = Variable(audioSource.value); + } + if (youtubeClientEngine.present) { + map['youtube_client_engine'] = + Variable(youtubeClientEngine.value); + } + if (streamMusicCodec.present) { + map['stream_music_codec'] = Variable(streamMusicCodec.value); + } + if (downloadMusicCodec.present) { + map['download_music_codec'] = Variable(downloadMusicCodec.value); + } + if (discordPresence.present) { + map['discord_presence'] = Variable(discordPresence.value); + } + if (endlessPlayback.present) { + map['endless_playback'] = Variable(endlessPlayback.value); + } + if (enableConnect.present) { + map['enable_connect'] = Variable(enableConnect.value); + } + if (connectPort.present) { + map['connect_port'] = Variable(connectPort.value); + } + if (cacheMusic.present) { + map['cache_music'] = Variable(cacheMusic.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PreferencesTableCompanion(') + ..write('id: $id, ') + ..write('audioQuality: $audioQuality, ') + ..write('albumColorSync: $albumColorSync, ') + ..write('amoledDarkTheme: $amoledDarkTheme, ') + ..write('checkUpdate: $checkUpdate, ') + ..write('normalizeAudio: $normalizeAudio, ') + ..write('showSystemTrayIcon: $showSystemTrayIcon, ') + ..write('systemTitleBar: $systemTitleBar, ') + ..write('skipNonMusic: $skipNonMusic, ') + ..write('closeBehavior: $closeBehavior, ') + ..write('accentColorScheme: $accentColorScheme, ') + ..write('layoutMode: $layoutMode, ') + ..write('locale: $locale, ') + ..write('market: $market, ') + ..write('searchMode: $searchMode, ') + ..write('downloadLocation: $downloadLocation, ') + ..write('localLibraryLocation: $localLibraryLocation, ') + ..write('pipedInstance: $pipedInstance, ') + ..write('invidiousInstance: $invidiousInstance, ') + ..write('themeMode: $themeMode, ') + ..write('audioSource: $audioSource, ') + ..write('youtubeClientEngine: $youtubeClientEngine, ') + ..write('streamMusicCodec: $streamMusicCodec, ') + ..write('downloadMusicCodec: $downloadMusicCodec, ') + ..write('discordPresence: $discordPresence, ') + ..write('endlessPlayback: $endlessPlayback, ') + ..write('enableConnect: $enableConnect, ') + ..write('connectPort: $connectPort, ') + ..write('cacheMusic: $cacheMusic') + ..write(')')) + .toString(); + } +} + +class ScrobblerTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + ScrobblerTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn username = GeneratedColumn( + 'username', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn passwordHash = GeneratedColumn( + 'password_hash', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, username, passwordHash]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'scrobbler_table'; + @override + Set get $primaryKey => {id}; + @override + ScrobblerTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return ScrobblerTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + username: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}username'])!, + passwordHash: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}password_hash'])!, + ); + } + + @override + ScrobblerTable createAlias(String alias) { + return ScrobblerTable(attachedDatabase, alias); + } +} + +class ScrobblerTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String username; + final String passwordHash; + const ScrobblerTableData( + {required this.id, + required this.createdAt, + required this.username, + required this.passwordHash}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['username'] = Variable(username); + map['password_hash'] = Variable(passwordHash); + return map; + } + + ScrobblerTableCompanion toCompanion(bool nullToAbsent) { + return ScrobblerTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + username: Value(username), + passwordHash: Value(passwordHash), + ); + } + + factory ScrobblerTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return ScrobblerTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + username: serializer.fromJson(json['username']), + passwordHash: serializer.fromJson(json['passwordHash']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'username': serializer.toJson(username), + 'passwordHash': serializer.toJson(passwordHash), + }; + } + + ScrobblerTableData copyWith( + {int? id, + DateTime? createdAt, + String? username, + String? passwordHash}) => + ScrobblerTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + ScrobblerTableData copyWithCompanion(ScrobblerTableCompanion data) { + return ScrobblerTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + username: data.username.present ? data.username.value : this.username, + passwordHash: data.passwordHash.present + ? data.passwordHash.value + : this.passwordHash, + ); + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, username, passwordHash); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is ScrobblerTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.username == this.username && + other.passwordHash == this.passwordHash); +} + +class ScrobblerTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value username; + final Value passwordHash; + const ScrobblerTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.username = const Value.absent(), + this.passwordHash = const Value.absent(), + }); + ScrobblerTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String username, + required String passwordHash, + }) : username = Value(username), + passwordHash = Value(passwordHash); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? username, + Expression? passwordHash, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (username != null) 'username': username, + if (passwordHash != null) 'password_hash': passwordHash, + }); + } + + ScrobblerTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? username, + Value? passwordHash}) { + return ScrobblerTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + username: username ?? this.username, + passwordHash: passwordHash ?? this.passwordHash, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (username.present) { + map['username'] = Variable(username.value); + } + if (passwordHash.present) { + map['password_hash'] = Variable(passwordHash.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('ScrobblerTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('username: $username, ') + ..write('passwordHash: $passwordHash') + ..write(')')) + .toString(); + } +} + +class SkipSegmentTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SkipSegmentTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn start = GeneratedColumn( + 'start', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn end = GeneratedColumn( + 'end', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => [id, start, end, trackId, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'skip_segment_table'; + @override + Set get $primaryKey => {id}; + @override + SkipSegmentTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SkipSegmentTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + start: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}start'])!, + end: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}end'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SkipSegmentTable createAlias(String alias) { + return SkipSegmentTable(attachedDatabase, alias); + } +} + +class SkipSegmentTableData extends DataClass + implements Insertable { + final int id; + final int start; + final int end; + final String trackId; + final DateTime createdAt; + const SkipSegmentTableData( + {required this.id, + required this.start, + required this.end, + required this.trackId, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['start'] = Variable(start); + map['end'] = Variable(end); + map['track_id'] = Variable(trackId); + map['created_at'] = Variable(createdAt); + return map; + } + + SkipSegmentTableCompanion toCompanion(bool nullToAbsent) { + return SkipSegmentTableCompanion( + id: Value(id), + start: Value(start), + end: Value(end), + trackId: Value(trackId), + createdAt: Value(createdAt), + ); + } + + factory SkipSegmentTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SkipSegmentTableData( + id: serializer.fromJson(json['id']), + start: serializer.fromJson(json['start']), + end: serializer.fromJson(json['end']), + trackId: serializer.fromJson(json['trackId']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'start': serializer.toJson(start), + 'end': serializer.toJson(end), + 'trackId': serializer.toJson(trackId), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SkipSegmentTableData copyWith( + {int? id, + int? start, + int? end, + String? trackId, + DateTime? createdAt}) => + SkipSegmentTableData( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + SkipSegmentTableData copyWithCompanion(SkipSegmentTableCompanion data) { + return SkipSegmentTableData( + id: data.id.present ? data.id.value : this.id, + start: data.start.present ? data.start.value : this.start, + end: data.end.present ? data.end.value : this.end, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableData(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, start, end, trackId, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SkipSegmentTableData && + other.id == this.id && + other.start == this.start && + other.end == this.end && + other.trackId == this.trackId && + other.createdAt == this.createdAt); +} + +class SkipSegmentTableCompanion extends UpdateCompanion { + final Value id; + final Value start; + final Value end; + final Value trackId; + final Value createdAt; + const SkipSegmentTableCompanion({ + this.id = const Value.absent(), + this.start = const Value.absent(), + this.end = const Value.absent(), + this.trackId = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SkipSegmentTableCompanion.insert({ + this.id = const Value.absent(), + required int start, + required int end, + required String trackId, + this.createdAt = const Value.absent(), + }) : start = Value(start), + end = Value(end), + trackId = Value(trackId); + static Insertable custom({ + Expression? id, + Expression? start, + Expression? end, + Expression? trackId, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (start != null) 'start': start, + if (end != null) 'end': end, + if (trackId != null) 'track_id': trackId, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SkipSegmentTableCompanion copyWith( + {Value? id, + Value? start, + Value? end, + Value? trackId, + Value? createdAt}) { + return SkipSegmentTableCompanion( + id: id ?? this.id, + start: start ?? this.start, + end: end ?? this.end, + trackId: trackId ?? this.trackId, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (start.present) { + map['start'] = Variable(start.value); + } + if (end.present) { + map['end'] = Variable(end.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SkipSegmentTableCompanion(') + ..write('id: $id, ') + ..write('start: $start, ') + ..write('end: $end, ') + ..write('trackId: $trackId, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class SourceMatchTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + SourceMatchTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceId = GeneratedColumn( + 'source_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn sourceType = GeneratedColumn( + 'source_type', aliasedName, false, + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: Constant(SourceType.youtube.name)); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + @override + List get $columns => + [id, trackId, sourceId, sourceType, createdAt]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'source_match_table'; + @override + Set get $primaryKey => {id}; + @override + SourceMatchTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return SourceMatchTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + sourceId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_id'])!, + sourceType: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}source_type'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + ); + } + + @override + SourceMatchTable createAlias(String alias) { + return SourceMatchTable(attachedDatabase, alias); + } +} + +class SourceMatchTableData extends DataClass + implements Insertable { + final int id; + final String trackId; + final String sourceId; + final String sourceType; + final DateTime createdAt; + const SourceMatchTableData( + {required this.id, + required this.trackId, + required this.sourceId, + required this.sourceType, + required this.createdAt}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['source_id'] = Variable(sourceId); + map['source_type'] = Variable(sourceType); + map['created_at'] = Variable(createdAt); + return map; + } + + SourceMatchTableCompanion toCompanion(bool nullToAbsent) { + return SourceMatchTableCompanion( + id: Value(id), + trackId: Value(trackId), + sourceId: Value(sourceId), + sourceType: Value(sourceType), + createdAt: Value(createdAt), + ); + } + + factory SourceMatchTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return SourceMatchTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + sourceId: serializer.fromJson(json['sourceId']), + sourceType: serializer.fromJson(json['sourceType']), + createdAt: serializer.fromJson(json['createdAt']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'sourceId': serializer.toJson(sourceId), + 'sourceType': serializer.toJson(sourceType), + 'createdAt': serializer.toJson(createdAt), + }; + } + + SourceMatchTableData copyWith( + {int? id, + String? trackId, + String? sourceId, + String? sourceType, + DateTime? createdAt}) => + SourceMatchTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceId: sourceId ?? this.sourceId, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + SourceMatchTableData copyWithCompanion(SourceMatchTableCompanion data) { + return SourceMatchTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + sourceId: data.sourceId.present ? data.sourceId.value : this.sourceId, + sourceType: + data.sourceType.present ? data.sourceType.value : this.sourceType, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + ); + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceId: $sourceId, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, trackId, sourceId, sourceType, createdAt); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is SourceMatchTableData && + other.id == this.id && + other.trackId == this.trackId && + other.sourceId == this.sourceId && + other.sourceType == this.sourceType && + other.createdAt == this.createdAt); +} + +class SourceMatchTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value sourceId; + final Value sourceType; + final Value createdAt; + const SourceMatchTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.sourceId = const Value.absent(), + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }); + SourceMatchTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String sourceId, + this.sourceType = const Value.absent(), + this.createdAt = const Value.absent(), + }) : trackId = Value(trackId), + sourceId = Value(sourceId); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? sourceId, + Expression? sourceType, + Expression? createdAt, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (sourceId != null) 'source_id': sourceId, + if (sourceType != null) 'source_type': sourceType, + if (createdAt != null) 'created_at': createdAt, + }); + } + + SourceMatchTableCompanion copyWith( + {Value? id, + Value? trackId, + Value? sourceId, + Value? sourceType, + Value? createdAt}) { + return SourceMatchTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + sourceId: sourceId ?? this.sourceId, + sourceType: sourceType ?? this.sourceType, + createdAt: createdAt ?? this.createdAt, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (sourceId.present) { + map['source_id'] = Variable(sourceId.value); + } + if (sourceType.present) { + map['source_type'] = Variable(sourceType.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('SourceMatchTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('sourceId: $sourceId, ') + ..write('sourceType: $sourceType, ') + ..write('createdAt: $createdAt') + ..write(')')) + .toString(); + } +} + +class AudioPlayerStateTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + AudioPlayerStateTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn playing = GeneratedColumn( + 'playing', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); + late final GeneratedColumn loopMode = GeneratedColumn( + 'loop_mode', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn shuffled = GeneratedColumn( + 'shuffled', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); + late final GeneratedColumn collections = GeneratedColumn( + 'collections', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => + [id, playing, loopMode, shuffled, collections]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'audio_player_state_table'; + @override + Set get $primaryKey => {id}; + @override + AudioPlayerStateTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return AudioPlayerStateTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playing: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}playing'])!, + loopMode: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}loop_mode'])!, + shuffled: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, + collections: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}collections'])!, + ); + } + + @override + AudioPlayerStateTable createAlias(String alias) { + return AudioPlayerStateTable(attachedDatabase, alias); + } +} + +class AudioPlayerStateTableData extends DataClass + implements Insertable { + final int id; + final bool playing; + final String loopMode; + final bool shuffled; + final String collections; + const AudioPlayerStateTableData( + {required this.id, + required this.playing, + required this.loopMode, + required this.shuffled, + required this.collections}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playing'] = Variable(playing); + map['loop_mode'] = Variable(loopMode); + map['shuffled'] = Variable(shuffled); + map['collections'] = Variable(collections); + return map; + } + + AudioPlayerStateTableCompanion toCompanion(bool nullToAbsent) { + return AudioPlayerStateTableCompanion( + id: Value(id), + playing: Value(playing), + loopMode: Value(loopMode), + shuffled: Value(shuffled), + collections: Value(collections), + ); + } + + factory AudioPlayerStateTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return AudioPlayerStateTableData( + id: serializer.fromJson(json['id']), + playing: serializer.fromJson(json['playing']), + loopMode: serializer.fromJson(json['loopMode']), + shuffled: serializer.fromJson(json['shuffled']), + collections: serializer.fromJson(json['collections']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playing': serializer.toJson(playing), + 'loopMode': serializer.toJson(loopMode), + 'shuffled': serializer.toJson(shuffled), + 'collections': serializer.toJson(collections), + }; + } + + AudioPlayerStateTableData copyWith( + {int? id, + bool? playing, + String? loopMode, + bool? shuffled, + String? collections}) => + AudioPlayerStateTableData( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + ); + AudioPlayerStateTableData copyWithCompanion( + AudioPlayerStateTableCompanion data) { + return AudioPlayerStateTableData( + id: data.id.present ? data.id.value : this.id, + playing: data.playing.present ? data.playing.value : this.playing, + loopMode: data.loopMode.present ? data.loopMode.value : this.loopMode, + shuffled: data.shuffled.present ? data.shuffled.value : this.shuffled, + collections: + data.collections.present ? data.collections.value : this.collections, + ); + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableData(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, playing, loopMode, shuffled, collections); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is AudioPlayerStateTableData && + other.id == this.id && + other.playing == this.playing && + other.loopMode == this.loopMode && + other.shuffled == this.shuffled && + other.collections == this.collections); +} + +class AudioPlayerStateTableCompanion + extends UpdateCompanion { + final Value id; + final Value playing; + final Value loopMode; + final Value shuffled; + final Value collections; + const AudioPlayerStateTableCompanion({ + this.id = const Value.absent(), + this.playing = const Value.absent(), + this.loopMode = const Value.absent(), + this.shuffled = const Value.absent(), + this.collections = const Value.absent(), + }); + AudioPlayerStateTableCompanion.insert({ + this.id = const Value.absent(), + required bool playing, + required String loopMode, + required bool shuffled, + required String collections, + }) : playing = Value(playing), + loopMode = Value(loopMode), + shuffled = Value(shuffled), + collections = Value(collections); + static Insertable custom({ + Expression? id, + Expression? playing, + Expression? loopMode, + Expression? shuffled, + Expression? collections, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playing != null) 'playing': playing, + if (loopMode != null) 'loop_mode': loopMode, + if (shuffled != null) 'shuffled': shuffled, + if (collections != null) 'collections': collections, + }); + } + + AudioPlayerStateTableCompanion copyWith( + {Value? id, + Value? playing, + Value? loopMode, + Value? shuffled, + Value? collections}) { + return AudioPlayerStateTableCompanion( + id: id ?? this.id, + playing: playing ?? this.playing, + loopMode: loopMode ?? this.loopMode, + shuffled: shuffled ?? this.shuffled, + collections: collections ?? this.collections, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playing.present) { + map['playing'] = Variable(playing.value); + } + if (loopMode.present) { + map['loop_mode'] = Variable(loopMode.value); + } + if (shuffled.present) { + map['shuffled'] = Variable(shuffled.value); + } + if (collections.present) { + map['collections'] = Variable(collections.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('AudioPlayerStateTableCompanion(') + ..write('id: $id, ') + ..write('playing: $playing, ') + ..write('loopMode: $loopMode, ') + ..write('shuffled: $shuffled, ') + ..write('collections: $collections') + ..write(')')) + .toString(); + } +} + +class PlaylistTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PlaylistTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn audioPlayerStateId = GeneratedColumn( + 'audio_player_state_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: GeneratedColumn.constraintIsAlways( + 'REFERENCES audio_player_state_table (id)')); + late final GeneratedColumn index = GeneratedColumn( + 'index', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); + @override + List get $columns => [id, audioPlayerStateId, index]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'playlist_table'; + @override + Set get $primaryKey => {id}; + @override + PlaylistTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlaylistTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + audioPlayerStateId: attachedDatabase.typeMapping.read( + DriftSqlType.int, data['${effectivePrefix}audio_player_state_id'])!, + index: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}index'])!, + ); + } + + @override + PlaylistTable createAlias(String alias) { + return PlaylistTable(attachedDatabase, alias); + } +} + +class PlaylistTableData extends DataClass + implements Insertable { + final int id; + final int audioPlayerStateId; + final int index; + const PlaylistTableData( + {required this.id, + required this.audioPlayerStateId, + required this.index}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['audio_player_state_id'] = Variable(audioPlayerStateId); + map['index'] = Variable(index); + return map; + } + + PlaylistTableCompanion toCompanion(bool nullToAbsent) { + return PlaylistTableCompanion( + id: Value(id), + audioPlayerStateId: Value(audioPlayerStateId), + index: Value(index), + ); + } + + factory PlaylistTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlaylistTableData( + id: serializer.fromJson(json['id']), + audioPlayerStateId: serializer.fromJson(json['audioPlayerStateId']), + index: serializer.fromJson(json['index']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'audioPlayerStateId': serializer.toJson(audioPlayerStateId), + 'index': serializer.toJson(index), + }; + } + + PlaylistTableData copyWith({int? id, int? audioPlayerStateId, int? index}) => + PlaylistTableData( + id: id ?? this.id, + audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, + index: index ?? this.index, + ); + PlaylistTableData copyWithCompanion(PlaylistTableCompanion data) { + return PlaylistTableData( + id: data.id.present ? data.id.value : this.id, + audioPlayerStateId: data.audioPlayerStateId.present + ? data.audioPlayerStateId.value + : this.audioPlayerStateId, + index: data.index.present ? data.index.value : this.index, + ); + } + + @override + String toString() { + return (StringBuffer('PlaylistTableData(') + ..write('id: $id, ') + ..write('audioPlayerStateId: $audioPlayerStateId, ') + ..write('index: $index') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, audioPlayerStateId, index); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlaylistTableData && + other.id == this.id && + other.audioPlayerStateId == this.audioPlayerStateId && + other.index == this.index); +} + +class PlaylistTableCompanion extends UpdateCompanion { + final Value id; + final Value audioPlayerStateId; + final Value index; + const PlaylistTableCompanion({ + this.id = const Value.absent(), + this.audioPlayerStateId = const Value.absent(), + this.index = const Value.absent(), + }); + PlaylistTableCompanion.insert({ + this.id = const Value.absent(), + required int audioPlayerStateId, + required int index, + }) : audioPlayerStateId = Value(audioPlayerStateId), + index = Value(index); + static Insertable custom({ + Expression? id, + Expression? audioPlayerStateId, + Expression? index, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (audioPlayerStateId != null) + 'audio_player_state_id': audioPlayerStateId, + if (index != null) 'index': index, + }); + } + + PlaylistTableCompanion copyWith( + {Value? id, Value? audioPlayerStateId, Value? index}) { + return PlaylistTableCompanion( + id: id ?? this.id, + audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, + index: index ?? this.index, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (audioPlayerStateId.present) { + map['audio_player_state_id'] = Variable(audioPlayerStateId.value); + } + if (index.present) { + map['index'] = Variable(index.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlaylistTableCompanion(') + ..write('id: $id, ') + ..write('audioPlayerStateId: $audioPlayerStateId, ') + ..write('index: $index') + ..write(')')) + .toString(); + } +} + +class PlaylistMediaTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + PlaylistMediaTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn playlistId = GeneratedColumn( + 'playlist_id', aliasedName, false, + type: DriftSqlType.int, + requiredDuringInsert: true, + defaultConstraints: + GeneratedColumn.constraintIsAlways('REFERENCES playlist_table (id)')); + late final GeneratedColumn uri = GeneratedColumn( + 'uri', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn extras = GeneratedColumn( + 'extras', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + late final GeneratedColumn httpHeaders = GeneratedColumn( + 'http_headers', aliasedName, true, + type: DriftSqlType.string, requiredDuringInsert: false); + @override + List get $columns => + [id, playlistId, uri, extras, httpHeaders]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'playlist_media_table'; + @override + Set get $primaryKey => {id}; + @override + PlaylistMediaTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return PlaylistMediaTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + playlistId: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}playlist_id'])!, + uri: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}uri'])!, + extras: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}extras']), + httpHeaders: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}http_headers']), + ); + } + + @override + PlaylistMediaTable createAlias(String alias) { + return PlaylistMediaTable(attachedDatabase, alias); + } +} + +class PlaylistMediaTableData extends DataClass + implements Insertable { + final int id; + final int playlistId; + final String uri; + final String? extras; + final String? httpHeaders; + const PlaylistMediaTableData( + {required this.id, + required this.playlistId, + required this.uri, + this.extras, + this.httpHeaders}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['playlist_id'] = Variable(playlistId); + map['uri'] = Variable(uri); + if (!nullToAbsent || extras != null) { + map['extras'] = Variable(extras); + } + if (!nullToAbsent || httpHeaders != null) { + map['http_headers'] = Variable(httpHeaders); + } + return map; + } + + PlaylistMediaTableCompanion toCompanion(bool nullToAbsent) { + return PlaylistMediaTableCompanion( + id: Value(id), + playlistId: Value(playlistId), + uri: Value(uri), + extras: + extras == null && nullToAbsent ? const Value.absent() : Value(extras), + httpHeaders: httpHeaders == null && nullToAbsent + ? const Value.absent() + : Value(httpHeaders), + ); + } + + factory PlaylistMediaTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return PlaylistMediaTableData( + id: serializer.fromJson(json['id']), + playlistId: serializer.fromJson(json['playlistId']), + uri: serializer.fromJson(json['uri']), + extras: serializer.fromJson(json['extras']), + httpHeaders: serializer.fromJson(json['httpHeaders']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'playlistId': serializer.toJson(playlistId), + 'uri': serializer.toJson(uri), + 'extras': serializer.toJson(extras), + 'httpHeaders': serializer.toJson(httpHeaders), + }; + } + + PlaylistMediaTableData copyWith( + {int? id, + int? playlistId, + String? uri, + Value extras = const Value.absent(), + Value httpHeaders = const Value.absent()}) => + PlaylistMediaTableData( + id: id ?? this.id, + playlistId: playlistId ?? this.playlistId, + uri: uri ?? this.uri, + extras: extras.present ? extras.value : this.extras, + httpHeaders: httpHeaders.present ? httpHeaders.value : this.httpHeaders, + ); + PlaylistMediaTableData copyWithCompanion(PlaylistMediaTableCompanion data) { + return PlaylistMediaTableData( + id: data.id.present ? data.id.value : this.id, + playlistId: + data.playlistId.present ? data.playlistId.value : this.playlistId, + uri: data.uri.present ? data.uri.value : this.uri, + extras: data.extras.present ? data.extras.value : this.extras, + httpHeaders: + data.httpHeaders.present ? data.httpHeaders.value : this.httpHeaders, + ); + } + + @override + String toString() { + return (StringBuffer('PlaylistMediaTableData(') + ..write('id: $id, ') + ..write('playlistId: $playlistId, ') + ..write('uri: $uri, ') + ..write('extras: $extras, ') + ..write('httpHeaders: $httpHeaders') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, playlistId, uri, extras, httpHeaders); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is PlaylistMediaTableData && + other.id == this.id && + other.playlistId == this.playlistId && + other.uri == this.uri && + other.extras == this.extras && + other.httpHeaders == this.httpHeaders); +} + +class PlaylistMediaTableCompanion + extends UpdateCompanion { + final Value id; + final Value playlistId; + final Value uri; + final Value extras; + final Value httpHeaders; + const PlaylistMediaTableCompanion({ + this.id = const Value.absent(), + this.playlistId = const Value.absent(), + this.uri = const Value.absent(), + this.extras = const Value.absent(), + this.httpHeaders = const Value.absent(), + }); + PlaylistMediaTableCompanion.insert({ + this.id = const Value.absent(), + required int playlistId, + required String uri, + this.extras = const Value.absent(), + this.httpHeaders = const Value.absent(), + }) : playlistId = Value(playlistId), + uri = Value(uri); + static Insertable custom({ + Expression? id, + Expression? playlistId, + Expression? uri, + Expression? extras, + Expression? httpHeaders, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (playlistId != null) 'playlist_id': playlistId, + if (uri != null) 'uri': uri, + if (extras != null) 'extras': extras, + if (httpHeaders != null) 'http_headers': httpHeaders, + }); + } + + PlaylistMediaTableCompanion copyWith( + {Value? id, + Value? playlistId, + Value? uri, + Value? extras, + Value? httpHeaders}) { + return PlaylistMediaTableCompanion( + id: id ?? this.id, + playlistId: playlistId ?? this.playlistId, + uri: uri ?? this.uri, + extras: extras ?? this.extras, + httpHeaders: httpHeaders ?? this.httpHeaders, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (playlistId.present) { + map['playlist_id'] = Variable(playlistId.value); + } + if (uri.present) { + map['uri'] = Variable(uri.value); + } + if (extras.present) { + map['extras'] = Variable(extras.value); + } + if (httpHeaders.present) { + map['http_headers'] = Variable(httpHeaders.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('PlaylistMediaTableCompanion(') + ..write('id: $id, ') + ..write('playlistId: $playlistId, ') + ..write('uri: $uri, ') + ..write('extras: $extras, ') + ..write('httpHeaders: $httpHeaders') + ..write(')')) + .toString(); + } +} + +class HistoryTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + HistoryTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn createdAt = GeneratedColumn( + 'created_at', aliasedName, false, + type: DriftSqlType.dateTime, + requiredDuringInsert: false, + defaultValue: currentDateAndTime); + late final GeneratedColumn type = GeneratedColumn( + 'type', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn itemId = GeneratedColumn( + 'item_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, createdAt, type, itemId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'history_table'; + @override + Set get $primaryKey => {id}; + @override + HistoryTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return HistoryTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + createdAt: attachedDatabase.typeMapping + .read(DriftSqlType.dateTime, data['${effectivePrefix}created_at'])!, + type: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}type'])!, + itemId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}item_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + HistoryTable createAlias(String alias) { + return HistoryTable(attachedDatabase, alias); + } +} + +class HistoryTableData extends DataClass + implements Insertable { + final int id; + final DateTime createdAt; + final String type; + final String itemId; + final String data; + const HistoryTableData( + {required this.id, + required this.createdAt, + required this.type, + required this.itemId, + required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['created_at'] = Variable(createdAt); + map['type'] = Variable(type); + map['item_id'] = Variable(itemId); + map['data'] = Variable(data); + return map; + } + + HistoryTableCompanion toCompanion(bool nullToAbsent) { + return HistoryTableCompanion( + id: Value(id), + createdAt: Value(createdAt), + type: Value(type), + itemId: Value(itemId), + data: Value(data), + ); + } + + factory HistoryTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return HistoryTableData( + id: serializer.fromJson(json['id']), + createdAt: serializer.fromJson(json['createdAt']), + type: serializer.fromJson(json['type']), + itemId: serializer.fromJson(json['itemId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'createdAt': serializer.toJson(createdAt), + 'type': serializer.toJson(type), + 'itemId': serializer.toJson(itemId), + 'data': serializer.toJson(data), + }; + } + + HistoryTableData copyWith( + {int? id, + DateTime? createdAt, + String? type, + String? itemId, + String? data}) => + HistoryTableData( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + HistoryTableData copyWithCompanion(HistoryTableCompanion data) { + return HistoryTableData( + id: data.id.present ? data.id.value : this.id, + createdAt: data.createdAt.present ? data.createdAt.value : this.createdAt, + type: data.type.present ? data.type.value : this.type, + itemId: data.itemId.present ? data.itemId.value : this.itemId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('HistoryTableData(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, createdAt, type, itemId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is HistoryTableData && + other.id == this.id && + other.createdAt == this.createdAt && + other.type == this.type && + other.itemId == this.itemId && + other.data == this.data); +} + +class HistoryTableCompanion extends UpdateCompanion { + final Value id; + final Value createdAt; + final Value type; + final Value itemId; + final Value data; + const HistoryTableCompanion({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + this.type = const Value.absent(), + this.itemId = const Value.absent(), + this.data = const Value.absent(), + }); + HistoryTableCompanion.insert({ + this.id = const Value.absent(), + this.createdAt = const Value.absent(), + required String type, + required String itemId, + required String data, + }) : type = Value(type), + itemId = Value(itemId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? createdAt, + Expression? type, + Expression? itemId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (createdAt != null) 'created_at': createdAt, + if (type != null) 'type': type, + if (itemId != null) 'item_id': itemId, + if (data != null) 'data': data, + }); + } + + HistoryTableCompanion copyWith( + {Value? id, + Value? createdAt, + Value? type, + Value? itemId, + Value? data}) { + return HistoryTableCompanion( + id: id ?? this.id, + createdAt: createdAt ?? this.createdAt, + type: type ?? this.type, + itemId: itemId ?? this.itemId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (createdAt.present) { + map['created_at'] = Variable(createdAt.value); + } + if (type.present) { + map['type'] = Variable(type.value); + } + if (itemId.present) { + map['item_id'] = Variable(itemId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('HistoryTableCompanion(') + ..write('id: $id, ') + ..write('createdAt: $createdAt, ') + ..write('type: $type, ') + ..write('itemId: $itemId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class LyricsTable extends Table with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + LyricsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn trackId = GeneratedColumn( + 'track_id', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn data = GeneratedColumn( + 'data', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + @override + List get $columns => [id, trackId, data]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'lyrics_table'; + @override + Set get $primaryKey => {id}; + @override + LyricsTableData map(Map data, {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return LyricsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + trackId: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}track_id'])!, + data: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}data'])!, + ); + } + + @override + LyricsTable createAlias(String alias) { + return LyricsTable(attachedDatabase, alias); + } +} + +class LyricsTableData extends DataClass implements Insertable { + final int id; + final String trackId; + final String data; + const LyricsTableData( + {required this.id, required this.trackId, required this.data}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['track_id'] = Variable(trackId); + map['data'] = Variable(data); + return map; + } + + LyricsTableCompanion toCompanion(bool nullToAbsent) { + return LyricsTableCompanion( + id: Value(id), + trackId: Value(trackId), + data: Value(data), + ); + } + + factory LyricsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return LyricsTableData( + id: serializer.fromJson(json['id']), + trackId: serializer.fromJson(json['trackId']), + data: serializer.fromJson(json['data']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'trackId': serializer.toJson(trackId), + 'data': serializer.toJson(data), + }; + } + + LyricsTableData copyWith({int? id, String? trackId, String? data}) => + LyricsTableData( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + LyricsTableData copyWithCompanion(LyricsTableCompanion data) { + return LyricsTableData( + id: data.id.present ? data.id.value : this.id, + trackId: data.trackId.present ? data.trackId.value : this.trackId, + data: data.data.present ? data.data.value : this.data, + ); + } + + @override + String toString() { + return (StringBuffer('LyricsTableData(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } + + @override + int get hashCode => Object.hash(id, trackId, data); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is LyricsTableData && + other.id == this.id && + other.trackId == this.trackId && + other.data == this.data); +} + +class LyricsTableCompanion extends UpdateCompanion { + final Value id; + final Value trackId; + final Value data; + const LyricsTableCompanion({ + this.id = const Value.absent(), + this.trackId = const Value.absent(), + this.data = const Value.absent(), + }); + LyricsTableCompanion.insert({ + this.id = const Value.absent(), + required String trackId, + required String data, + }) : trackId = Value(trackId), + data = Value(data); + static Insertable custom({ + Expression? id, + Expression? trackId, + Expression? data, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (trackId != null) 'track_id': trackId, + if (data != null) 'data': data, + }); + } + + LyricsTableCompanion copyWith( + {Value? id, Value? trackId, Value? data}) { + return LyricsTableCompanion( + id: id ?? this.id, + trackId: trackId ?? this.trackId, + data: data ?? this.data, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (trackId.present) { + map['track_id'] = Variable(trackId.value); + } + if (data.present) { + map['data'] = Variable(data.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('LyricsTableCompanion(') + ..write('id: $id, ') + ..write('trackId: $trackId, ') + ..write('data: $data') + ..write(')')) + .toString(); + } +} + +class MetadataPluginsTable extends Table + with TableInfo { + @override + final GeneratedDatabase attachedDatabase; + final String? _alias; + MetadataPluginsTable(this.attachedDatabase, [this._alias]); + late final GeneratedColumn id = GeneratedColumn( + 'id', aliasedName, false, + hasAutoIncrement: true, + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); + late final GeneratedColumn name = GeneratedColumn( + 'name', aliasedName, false, + additionalChecks: + GeneratedColumn.checkTextLength(minTextLength: 1, maxTextLength: 50), + type: DriftSqlType.string, + requiredDuringInsert: true); + late final GeneratedColumn description = GeneratedColumn( + 'description', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn version = GeneratedColumn( + 'version', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn author = GeneratedColumn( + 'author', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn selected = GeneratedColumn( + 'selected', aliasedName, false, + type: DriftSqlType.bool, + requiredDuringInsert: false, + defaultConstraints: + GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), + defaultValue: const Constant(false)); + @override + List get $columns => + [id, name, description, version, author, selected]; + @override + String get aliasedName => _alias ?? actualTableName; + @override + String get actualTableName => $name; + static const String $name = 'metadata_plugins_table'; + @override + Set get $primaryKey => {id}; + @override + MetadataPluginsTableData map(Map data, + {String? tablePrefix}) { + final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; + return MetadataPluginsTableData( + id: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}id'])!, + name: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}name'])!, + description: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}description'])!, + version: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}version'])!, + author: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + selected: attachedDatabase.typeMapping + .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, + ); + } + + @override + MetadataPluginsTable createAlias(String alias) { + return MetadataPluginsTable(attachedDatabase, alias); + } +} + +class MetadataPluginsTableData extends DataClass + implements Insertable { + final int id; + final String name; + final String description; + final String version; + final String author; + final bool selected; + const MetadataPluginsTableData( + {required this.id, + required this.name, + required this.description, + required this.version, + required this.author, + required this.selected}); + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + map['id'] = Variable(id); + map['name'] = Variable(name); + map['description'] = Variable(description); + map['version'] = Variable(version); + map['author'] = Variable(author); + map['selected'] = Variable(selected); + return map; + } + + MetadataPluginsTableCompanion toCompanion(bool nullToAbsent) { + return MetadataPluginsTableCompanion( + id: Value(id), + name: Value(name), + description: Value(description), + version: Value(version), + author: Value(author), + selected: Value(selected), + ); + } + + factory MetadataPluginsTableData.fromJson(Map json, + {ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return MetadataPluginsTableData( + id: serializer.fromJson(json['id']), + name: serializer.fromJson(json['name']), + description: serializer.fromJson(json['description']), + version: serializer.fromJson(json['version']), + author: serializer.fromJson(json['author']), + selected: serializer.fromJson(json['selected']), + ); + } + @override + Map toJson({ValueSerializer? serializer}) { + serializer ??= driftRuntimeOptions.defaultSerializer; + return { + 'id': serializer.toJson(id), + 'name': serializer.toJson(name), + 'description': serializer.toJson(description), + 'version': serializer.toJson(version), + 'author': serializer.toJson(author), + 'selected': serializer.toJson(selected), + }; + } + + MetadataPluginsTableData copyWith( + {int? id, + String? name, + String? description, + String? version, + String? author, + bool? selected}) => + MetadataPluginsTableData( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + selected: selected ?? this.selected, + ); + MetadataPluginsTableData copyWithCompanion( + MetadataPluginsTableCompanion data) { + return MetadataPluginsTableData( + id: data.id.present ? data.id.value : this.id, + name: data.name.present ? data.name.value : this.name, + description: + data.description.present ? data.description.value : this.description, + version: data.version.present ? data.version.value : this.version, + author: data.author.present ? data.author.value : this.author, + selected: data.selected.present ? data.selected.value : this.selected, + ); + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableData(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('selected: $selected') + ..write(')')) + .toString(); + } + + @override + int get hashCode => + Object.hash(id, name, description, version, author, selected); + @override + bool operator ==(Object other) => + identical(this, other) || + (other is MetadataPluginsTableData && + other.id == this.id && + other.name == this.name && + other.description == this.description && + other.version == this.version && + other.author == this.author && + other.selected == this.selected); +} + +class MetadataPluginsTableCompanion + extends UpdateCompanion { + final Value id; + final Value name; + final Value description; + final Value version; + final Value author; + final Value selected; + const MetadataPluginsTableCompanion({ + this.id = const Value.absent(), + this.name = const Value.absent(), + this.description = const Value.absent(), + this.version = const Value.absent(), + this.author = const Value.absent(), + this.selected = const Value.absent(), + }); + MetadataPluginsTableCompanion.insert({ + this.id = const Value.absent(), + required String name, + required String description, + required String version, + required String author, + this.selected = const Value.absent(), + }) : name = Value(name), + description = Value(description), + version = Value(version), + author = Value(author); + static Insertable custom({ + Expression? id, + Expression? name, + Expression? description, + Expression? version, + Expression? author, + Expression? selected, + }) { + return RawValuesInsertable({ + if (id != null) 'id': id, + if (name != null) 'name': name, + if (description != null) 'description': description, + if (version != null) 'version': version, + if (author != null) 'author': author, + if (selected != null) 'selected': selected, + }); + } + + MetadataPluginsTableCompanion copyWith( + {Value? id, + Value? name, + Value? description, + Value? version, + Value? author, + Value? selected}) { + return MetadataPluginsTableCompanion( + id: id ?? this.id, + name: name ?? this.name, + description: description ?? this.description, + version: version ?? this.version, + author: author ?? this.author, + selected: selected ?? this.selected, + ); + } + + @override + Map toColumns(bool nullToAbsent) { + final map = {}; + if (id.present) { + map['id'] = Variable(id.value); + } + if (name.present) { + map['name'] = Variable(name.value); + } + if (description.present) { + map['description'] = Variable(description.value); + } + if (version.present) { + map['version'] = Variable(version.value); + } + if (author.present) { + map['author'] = Variable(author.value); + } + if (selected.present) { + map['selected'] = Variable(selected.value); + } + return map; + } + + @override + String toString() { + return (StringBuffer('MetadataPluginsTableCompanion(') + ..write('id: $id, ') + ..write('name: $name, ') + ..write('description: $description, ') + ..write('version: $version, ') + ..write('author: $author, ') + ..write('selected: $selected') + ..write(')')) + .toString(); + } +} + +class DatabaseAtV7 extends GeneratedDatabase { + DatabaseAtV7(QueryExecutor e) : super(e); + late final AuthenticationTable authenticationTable = + AuthenticationTable(this); + late final BlacklistTable blacklistTable = BlacklistTable(this); + late final PreferencesTable preferencesTable = PreferencesTable(this); + late final ScrobblerTable scrobblerTable = ScrobblerTable(this); + late final SkipSegmentTable skipSegmentTable = SkipSegmentTable(this); + late final SourceMatchTable sourceMatchTable = SourceMatchTable(this); + late final AudioPlayerStateTable audioPlayerStateTable = + AudioPlayerStateTable(this); + late final PlaylistTable playlistTable = PlaylistTable(this); + late final PlaylistMediaTable playlistMediaTable = PlaylistMediaTable(this); + late final HistoryTable historyTable = HistoryTable(this); + late final LyricsTable lyricsTable = LyricsTable(this); + late final MetadataPluginsTable metadataPluginsTable = + MetadataPluginsTable(this); + late final Index uniqueBlacklist = Index('unique_blacklist', + 'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)'); + late final Index uniqTrackMatch = Index('uniq_track_match', + 'CREATE UNIQUE INDEX uniq_track_match ON source_match_table (track_id, source_id, source_type)'); + @override + Iterable> get allTables => + allSchemaEntities.whereType>(); + @override + List get allSchemaEntities => [ + authenticationTable, + blacklistTable, + preferencesTable, + scrobblerTable, + skipSegmentTable, + sourceMatchTable, + audioPlayerStateTable, + playlistTable, + playlistMediaTable, + historyTable, + lyricsTable, + metadataPluginsTable, + uniqueBlacklist, + uniqTrackMatch + ]; + @override + int get schemaVersion => 7; +} From 69c0333327d6910ee12cb4ed602e9e1a7bc1aa3e Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 13 Jun 2025 17:00:59 +0600 Subject: [PATCH 09/60] feat: update plugin configuration with more fields --- drift_schemas/app_db/drift_schema_v7.json | 2 +- lib/models/database/database.g.dart | 191 +++++++++++++++++- lib/models/database/database.steps.dart | 20 +- .../database/tables/metadata_plugins.dart | 3 + lib/models/metadata/metadata.freezed.dart | 103 +++++++++- lib/models/metadata/metadata.g.dart | 23 +++ lib/models/metadata/plugin.dart | 7 + .../metadata_plugin_provider.dart | 20 ++ test/drift/app_db/generated/schema_v7.dart | 104 +++++++++- 9 files changed, 452 insertions(+), 21 deletions(-) diff --git a/drift_schemas/app_db/drift_schema_v7.json b/drift_schemas/app_db/drift_schema_v7.json index d42e0b84..4390b550 100644 --- a/drift_schemas/app_db/drift_schema_v7.json +++ b/drift_schemas/app_db/drift_schema_v7.json @@ -1 +1 @@ -{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[6],"type":"table","data":{"name":"playlist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_player_state_id","getter_name":"audioPlayerStateId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES audio_player_state_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES audio_player_state_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"index","getter_name":"index","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[7],"type":"table","data":{"name":"playlist_media_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playlist_id","getter_name":"playlistId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES playlist_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES playlist_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"uri","getter_name":"uri","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"extras","getter_name":"extras","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}},{"name":"http_headers","getter_name":"httpHeaders","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":11,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":12,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":13,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[6],"type":"table","data":{"name":"playlist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_player_state_id","getter_name":"audioPlayerStateId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES audio_player_state_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES audio_player_state_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"index","getter_name":"index","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[7],"type":"table","data":{"name":"playlist_media_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playlist_id","getter_name":"playlistId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES playlist_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES playlist_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"uri","getter_name":"uri","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"extras","getter_name":"extras","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}},{"name":"http_headers","getter_name":"httpHeaders","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":11,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entry_point","getter_name":"entryPoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"apis","getter_name":"apis","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"abilities","getter_name":"abilities","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":12,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":13,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 210e8d49..02f75640 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -4315,6 +4315,27 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable late final GeneratedColumn author = GeneratedColumn( 'author', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _entryPointMeta = + const VerificationMeta('entryPoint'); + @override + late final GeneratedColumn entryPoint = GeneratedColumn( + 'entry_point', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + static const VerificationMeta _apisMeta = const VerificationMeta('apis'); + @override + late final GeneratedColumnWithTypeConverter, String> apis = + GeneratedColumn('apis', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $MetadataPluginsTableTable.$converterapis); + static const VerificationMeta _abilitiesMeta = + const VerificationMeta('abilities'); + @override + late final GeneratedColumnWithTypeConverter, String> abilities = + GeneratedColumn('abilities', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $MetadataPluginsTableTable.$converterabilities); static const VerificationMeta _selectedMeta = const VerificationMeta('selected'); @override @@ -4326,8 +4347,17 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), defaultValue: const Constant(false)); @override - List get $columns => - [id, name, description, version, author, selected]; + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selected + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -4368,6 +4398,16 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable } else if (isInserting) { context.missing(_authorMeta); } + if (data.containsKey('entry_point')) { + context.handle( + _entryPointMeta, + entryPoint.isAcceptableOrUnknown( + data['entry_point']!, _entryPointMeta)); + } else if (isInserting) { + context.missing(_entryPointMeta); + } + context.handle(_apisMeta, const VerificationResult.success()); + context.handle(_abilitiesMeta, const VerificationResult.success()); if (data.containsKey('selected')) { context.handle(_selectedMeta, selected.isAcceptableOrUnknown(data['selected']!, _selectedMeta)); @@ -4392,6 +4432,14 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable .read(DriftSqlType.string, data['${effectivePrefix}version'])!, author: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + entryPoint: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}entry_point'])!, + apis: $MetadataPluginsTableTable.$converterapis.fromSql(attachedDatabase + .typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}apis'])!), + abilities: $MetadataPluginsTableTable.$converterabilities.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!), selected: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, ); @@ -4401,6 +4449,11 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable $MetadataPluginsTableTable createAlias(String alias) { return $MetadataPluginsTableTable(attachedDatabase, alias); } + + static TypeConverter, String> $converterapis = + const StringListConverter(); + static TypeConverter, String> $converterabilities = + const StringListConverter(); } class MetadataPluginsTableData extends DataClass @@ -4410,6 +4463,9 @@ class MetadataPluginsTableData extends DataClass final String description; final String version; final String author; + final String entryPoint; + final List apis; + final List abilities; final bool selected; const MetadataPluginsTableData( {required this.id, @@ -4417,6 +4473,9 @@ class MetadataPluginsTableData extends DataClass required this.description, required this.version, required this.author, + required this.entryPoint, + required this.apis, + required this.abilities, required this.selected}); @override Map toColumns(bool nullToAbsent) { @@ -4426,6 +4485,15 @@ class MetadataPluginsTableData extends DataClass map['description'] = Variable(description); map['version'] = Variable(version); map['author'] = Variable(author); + map['entry_point'] = Variable(entryPoint); + { + map['apis'] = Variable( + $MetadataPluginsTableTable.$converterapis.toSql(apis)); + } + { + map['abilities'] = Variable( + $MetadataPluginsTableTable.$converterabilities.toSql(abilities)); + } map['selected'] = Variable(selected); return map; } @@ -4437,6 +4505,9 @@ class MetadataPluginsTableData extends DataClass description: Value(description), version: Value(version), author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), selected: Value(selected), ); } @@ -4450,6 +4521,9 @@ class MetadataPluginsTableData extends DataClass description: serializer.fromJson(json['description']), version: serializer.fromJson(json['version']), author: serializer.fromJson(json['author']), + entryPoint: serializer.fromJson(json['entryPoint']), + apis: serializer.fromJson>(json['apis']), + abilities: serializer.fromJson>(json['abilities']), selected: serializer.fromJson(json['selected']), ); } @@ -4462,6 +4536,9 @@ class MetadataPluginsTableData extends DataClass 'description': serializer.toJson(description), 'version': serializer.toJson(version), 'author': serializer.toJson(author), + 'entryPoint': serializer.toJson(entryPoint), + 'apis': serializer.toJson>(apis), + 'abilities': serializer.toJson>(abilities), 'selected': serializer.toJson(selected), }; } @@ -4472,6 +4549,9 @@ class MetadataPluginsTableData extends DataClass String? description, String? version, String? author, + String? entryPoint, + List? apis, + List? abilities, bool? selected}) => MetadataPluginsTableData( id: id ?? this.id, @@ -4479,6 +4559,9 @@ class MetadataPluginsTableData extends DataClass description: description ?? this.description, version: version ?? this.version, author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, selected: selected ?? this.selected, ); MetadataPluginsTableData copyWithCompanion( @@ -4490,6 +4573,10 @@ class MetadataPluginsTableData extends DataClass data.description.present ? data.description.value : this.description, version: data.version.present ? data.version.value : this.version, author: data.author.present ? data.author.value : this.author, + entryPoint: + data.entryPoint.present ? data.entryPoint.value : this.entryPoint, + apis: data.apis.present ? data.apis.value : this.apis, + abilities: data.abilities.present ? data.abilities.value : this.abilities, selected: data.selected.present ? data.selected.value : this.selected, ); } @@ -4502,14 +4589,17 @@ class MetadataPluginsTableData extends DataClass ..write('description: $description, ') ..write('version: $version, ') ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') ..write('selected: $selected') ..write(')')) .toString(); } @override - int get hashCode => - Object.hash(id, name, description, version, author, selected); + int get hashCode => Object.hash(id, name, description, version, author, + entryPoint, apis, abilities, selected); @override bool operator ==(Object other) => identical(this, other) || @@ -4519,6 +4609,9 @@ class MetadataPluginsTableData extends DataClass other.description == this.description && other.version == this.version && other.author == this.author && + other.entryPoint == this.entryPoint && + other.apis == this.apis && + other.abilities == this.abilities && other.selected == this.selected); } @@ -4529,6 +4622,9 @@ class MetadataPluginsTableCompanion final Value description; final Value version; final Value author; + final Value entryPoint; + final Value> apis; + final Value> abilities; final Value selected; const MetadataPluginsTableCompanion({ this.id = const Value.absent(), @@ -4536,6 +4632,9 @@ class MetadataPluginsTableCompanion this.description = const Value.absent(), this.version = const Value.absent(), this.author = const Value.absent(), + this.entryPoint = const Value.absent(), + this.apis = const Value.absent(), + this.abilities = const Value.absent(), this.selected = const Value.absent(), }); MetadataPluginsTableCompanion.insert({ @@ -4544,17 +4643,26 @@ class MetadataPluginsTableCompanion required String description, required String version, required String author, + required String entryPoint, + required List apis, + required List abilities, this.selected = const Value.absent(), }) : name = Value(name), description = Value(description), version = Value(version), - author = Value(author); + author = Value(author), + entryPoint = Value(entryPoint), + apis = Value(apis), + abilities = Value(abilities); static Insertable custom({ Expression? id, Expression? name, Expression? description, Expression? version, Expression? author, + Expression? entryPoint, + Expression? apis, + Expression? abilities, Expression? selected, }) { return RawValuesInsertable({ @@ -4563,6 +4671,9 @@ class MetadataPluginsTableCompanion if (description != null) 'description': description, if (version != null) 'version': version, if (author != null) 'author': author, + if (entryPoint != null) 'entry_point': entryPoint, + if (apis != null) 'apis': apis, + if (abilities != null) 'abilities': abilities, if (selected != null) 'selected': selected, }); } @@ -4573,6 +4684,9 @@ class MetadataPluginsTableCompanion Value? description, Value? version, Value? author, + Value? entryPoint, + Value>? apis, + Value>? abilities, Value? selected}) { return MetadataPluginsTableCompanion( id: id ?? this.id, @@ -4580,6 +4694,9 @@ class MetadataPluginsTableCompanion description: description ?? this.description, version: version ?? this.version, author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, selected: selected ?? this.selected, ); } @@ -4602,6 +4719,18 @@ class MetadataPluginsTableCompanion if (author.present) { map['author'] = Variable(author.value); } + if (entryPoint.present) { + map['entry_point'] = Variable(entryPoint.value); + } + if (apis.present) { + map['apis'] = Variable( + $MetadataPluginsTableTable.$converterapis.toSql(apis.value)); + } + if (abilities.present) { + map['abilities'] = Variable($MetadataPluginsTableTable + .$converterabilities + .toSql(abilities.value)); + } if (selected.present) { map['selected'] = Variable(selected.value); } @@ -4616,6 +4745,9 @@ class MetadataPluginsTableCompanion ..write('description: $description, ') ..write('version: $version, ') ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') ..write('selected: $selected') ..write(')')) .toString(); @@ -7266,6 +7398,9 @@ typedef $$MetadataPluginsTableTableCreateCompanionBuilder required String description, required String version, required String author, + required String entryPoint, + required List apis, + required List abilities, Value selected, }); typedef $$MetadataPluginsTableTableUpdateCompanionBuilder @@ -7275,6 +7410,9 @@ typedef $$MetadataPluginsTableTableUpdateCompanionBuilder Value description, Value version, Value author, + Value entryPoint, + Value> apis, + Value> abilities, Value selected, }); @@ -7302,6 +7440,19 @@ class $$MetadataPluginsTableTableFilterComposer ColumnFilters get author => $composableBuilder( column: $table.author, builder: (column) => ColumnFilters(column)); + ColumnFilters get entryPoint => $composableBuilder( + column: $table.entryPoint, builder: (column) => ColumnFilters(column)); + + ColumnWithTypeConverterFilters, List, String> get apis => + $composableBuilder( + column: $table.apis, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnWithTypeConverterFilters, List, String> + get abilities => $composableBuilder( + column: $table.abilities, + builder: (column) => ColumnWithTypeConverterFilters(column)); + ColumnFilters get selected => $composableBuilder( column: $table.selected, builder: (column) => ColumnFilters(column)); } @@ -7330,6 +7481,15 @@ class $$MetadataPluginsTableTableOrderingComposer ColumnOrderings get author => $composableBuilder( column: $table.author, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get entryPoint => $composableBuilder( + column: $table.entryPoint, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get apis => $composableBuilder( + column: $table.apis, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get abilities => $composableBuilder( + column: $table.abilities, builder: (column) => ColumnOrderings(column)); + ColumnOrderings get selected => $composableBuilder( column: $table.selected, builder: (column) => ColumnOrderings(column)); } @@ -7358,6 +7518,15 @@ class $$MetadataPluginsTableTableAnnotationComposer GeneratedColumn get author => $composableBuilder(column: $table.author, builder: (column) => column); + GeneratedColumn get entryPoint => $composableBuilder( + column: $table.entryPoint, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> get apis => + $composableBuilder(column: $table.apis, builder: (column) => column); + + GeneratedColumnWithTypeConverter, String> get abilities => + $composableBuilder(column: $table.abilities, builder: (column) => column); + GeneratedColumn get selected => $composableBuilder(column: $table.selected, builder: (column) => column); } @@ -7397,6 +7566,9 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< Value description = const Value.absent(), Value version = const Value.absent(), Value author = const Value.absent(), + Value entryPoint = const Value.absent(), + Value> apis = const Value.absent(), + Value> abilities = const Value.absent(), Value selected = const Value.absent(), }) => MetadataPluginsTableCompanion( @@ -7405,6 +7577,9 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< description: description, version: version, author: author, + entryPoint: entryPoint, + apis: apis, + abilities: abilities, selected: selected, ), createCompanionCallback: ({ @@ -7413,6 +7588,9 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< required String description, required String version, required String author, + required String entryPoint, + required List apis, + required List abilities, Value selected = const Value.absent(), }) => MetadataPluginsTableCompanion.insert( @@ -7421,6 +7599,9 @@ class $$MetadataPluginsTableTableTableManager extends RootTableManager< description: description, version: version, author: author, + entryPoint: entryPoint, + apis: apis, + abilities: abilities, selected: selected, ), withReferenceMapper: (p0) => p0 diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index 51873c60..beccaef8 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1918,6 +1918,9 @@ final class Schema7 extends i0.VersionedSchema { _column_59, _column_60, _column_61, + _column_62, + _column_63, + _column_64, ], attachedDatabase: database, ), @@ -1940,6 +1943,12 @@ class Shape14 extends i0.VersionedTable { columnsByName['version']! as i1.GeneratedColumn; i1.GeneratedColumn get author => columnsByName['author']! as i1.GeneratedColumn; + i1.GeneratedColumn get entryPoint => + columnsByName['entry_point']! as i1.GeneratedColumn; + i1.GeneratedColumn get apis => + columnsByName['apis']! as i1.GeneratedColumn; + i1.GeneratedColumn get abilities => + columnsByName['abilities']! as i1.GeneratedColumn; i1.GeneratedColumn get selected => columnsByName['selected']! as i1.GeneratedColumn; } @@ -1958,7 +1967,16 @@ i1.GeneratedColumn _column_59(String aliasedName) => i1.GeneratedColumn _column_60(String aliasedName) => i1.GeneratedColumn('author', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_61(String aliasedName) => +i1.GeneratedColumn _column_61(String aliasedName) => + i1.GeneratedColumn('entry_point', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_62(String aliasedName) => + i1.GeneratedColumn('apis', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_63(String aliasedName) => + i1.GeneratedColumn('abilities', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_64(String aliasedName) => i1.GeneratedColumn('selected', aliasedName, false, type: i1.DriftSqlType.bool, defaultConstraints: i1.GeneratedColumn.constraintIsAlways( diff --git a/lib/models/database/tables/metadata_plugins.dart b/lib/models/database/tables/metadata_plugins.dart index e2f49a92..2f243fc1 100644 --- a/lib/models/database/tables/metadata_plugins.dart +++ b/lib/models/database/tables/metadata_plugins.dart @@ -6,5 +6,8 @@ class MetadataPluginsTable extends Table { TextColumn get description => text()(); TextColumn get version => text()(); TextColumn get author => text()(); + TextColumn get entryPoint => text()(); + TextColumn get apis => text().map(const StringListConverter())(); + TextColumn get abilities => text().map(const StringListConverter())(); BoolColumn get selected => boolean().withDefault(const Constant(false))(); } diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index b5a2599f..f5544cb5 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -2442,6 +2442,9 @@ mixin _$PluginConfiguration { String get description => throw _privateConstructorUsedError; String get version => throw _privateConstructorUsedError; String get author => throw _privateConstructorUsedError; + String get entryPoint => throw _privateConstructorUsedError; + List get apis => throw _privateConstructorUsedError; + List get abilities => throw _privateConstructorUsedError; /// Serializes this PluginConfiguration to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -2464,7 +2467,10 @@ abstract class $PluginConfigurationCopyWith<$Res> { String name, String description, String version, - String author}); + String author, + String entryPoint, + List apis, + List abilities}); } /// @nodoc @@ -2487,6 +2493,9 @@ class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration> Object? description = null, Object? version = null, Object? author = null, + Object? entryPoint = null, + Object? apis = null, + Object? abilities = null, }) { return _then(_value.copyWith( type: null == type @@ -2509,6 +2518,18 @@ class _$PluginConfigurationCopyWithImpl<$Res, $Val extends PluginConfiguration> ? _value.author : author // ignore: cast_nullable_to_non_nullable as String, + entryPoint: null == entryPoint + ? _value.entryPoint + : entryPoint // ignore: cast_nullable_to_non_nullable + as String, + apis: null == apis + ? _value.apis + : apis // ignore: cast_nullable_to_non_nullable + as List, + abilities: null == abilities + ? _value.abilities + : abilities // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -2526,7 +2547,10 @@ abstract class _$$PluginConfigurationImplCopyWith<$Res> String name, String description, String version, - String author}); + String author, + String entryPoint, + List apis, + List abilities}); } /// @nodoc @@ -2547,6 +2571,9 @@ class __$$PluginConfigurationImplCopyWithImpl<$Res> Object? description = null, Object? version = null, Object? author = null, + Object? entryPoint = null, + Object? apis = null, + Object? abilities = null, }) { return _then(_$PluginConfigurationImpl( type: null == type @@ -2569,6 +2596,18 @@ class __$$PluginConfigurationImplCopyWithImpl<$Res> ? _value.author : author // ignore: cast_nullable_to_non_nullable as String, + entryPoint: null == entryPoint + ? _value.entryPoint + : entryPoint // ignore: cast_nullable_to_non_nullable + as String, + apis: null == apis + ? _value._apis + : apis // ignore: cast_nullable_to_non_nullable + as List, + abilities: null == abilities + ? _value._abilities + : abilities // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -2581,8 +2620,13 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { required this.name, required this.description, required this.version, - required this.author}) - : super._(); + required this.author, + required this.entryPoint, + final List apis = const [], + final List abilities = const []}) + : _apis = apis, + _abilities = abilities, + super._(); factory _$PluginConfigurationImpl.fromJson(Map json) => _$$PluginConfigurationImplFromJson(json); @@ -2597,10 +2641,29 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { final String version; @override final String author; + @override + final String entryPoint; + final List _apis; + @override + @JsonKey() + List get apis { + if (_apis is EqualUnmodifiableListView) return _apis; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_apis); + } + + final List _abilities; + @override + @JsonKey() + List get abilities { + if (_abilities is EqualUnmodifiableListView) return _abilities; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_abilities); + } @override String toString() { - return 'PluginConfiguration(type: $type, name: $name, description: $description, version: $version, author: $author)'; + return 'PluginConfiguration(type: $type, name: $name, description: $description, version: $version, author: $author, entryPoint: $entryPoint, apis: $apis, abilities: $abilities)'; } @override @@ -2613,13 +2676,26 @@ class _$PluginConfigurationImpl extends _PluginConfiguration { (identical(other.description, description) || other.description == description) && (identical(other.version, version) || other.version == version) && - (identical(other.author, author) || other.author == author)); + (identical(other.author, author) || other.author == author) && + (identical(other.entryPoint, entryPoint) || + other.entryPoint == entryPoint) && + const DeepCollectionEquality().equals(other._apis, _apis) && + const DeepCollectionEquality() + .equals(other._abilities, _abilities)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, type, name, description, version, author); + int get hashCode => Object.hash( + runtimeType, + type, + name, + description, + version, + author, + entryPoint, + const DeepCollectionEquality().hash(_apis), + const DeepCollectionEquality().hash(_abilities)); /// Create a copy of PluginConfiguration /// with the given fields replaced by the non-null parameter values. @@ -2644,7 +2720,10 @@ abstract class _PluginConfiguration extends PluginConfiguration { required final String name, required final String description, required final String version, - required final String author}) = _$PluginConfigurationImpl; + required final String author, + required final String entryPoint, + final List apis, + final List abilities}) = _$PluginConfigurationImpl; _PluginConfiguration._() : super._(); factory _PluginConfiguration.fromJson(Map json) = @@ -2660,6 +2739,12 @@ abstract class _PluginConfiguration extends PluginConfiguration { String get version; @override String get author; + @override + String get entryPoint; + @override + List get apis; + @override + List get abilities; /// Create a copy of PluginConfiguration /// 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 96c8119c..7ae2cc75 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -231,6 +231,15 @@ _$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) => description: json['description'] as String, version: json['version'] as String, author: json['author'] as String, + entryPoint: json['entryPoint'] as String, + apis: (json['apis'] as List?) + ?.map((e) => $enumDecode(_$PluginApisEnumMap, e)) + .toList() ?? + const [], + abilities: (json['abilities'] as List?) + ?.map((e) => $enumDecode(_$PluginAbilitiesEnumMap, e)) + .toList() ?? + const [], ); Map _$$PluginConfigurationImplToJson( @@ -241,8 +250,22 @@ Map _$$PluginConfigurationImplToJson( 'description': instance.description, 'version': instance.version, 'author': instance.author, + 'entryPoint': instance.entryPoint, + 'apis': instance.apis.map((e) => _$PluginApisEnumMap[e]!).toList(), + 'abilities': + instance.abilities.map((e) => _$PluginAbilitiesEnumMap[e]!).toList(), }; const _$PluginTypeEnumMap = { PluginType.metadata: 'metadata', }; + +const _$PluginApisEnumMap = { + PluginApis.webview: 'webview', + PluginApis.localstorage: 'localstorage', + PluginApis.timezone: 'timezone', +}; + +const _$PluginAbilitiesEnumMap = { + PluginAbilities.authentication: 'authentication', +}; diff --git a/lib/models/metadata/plugin.dart b/lib/models/metadata/plugin.dart index d6254168..fba14c08 100644 --- a/lib/models/metadata/plugin.dart +++ b/lib/models/metadata/plugin.dart @@ -2,6 +2,10 @@ part of 'metadata.dart'; enum PluginType { metadata } +enum PluginApis { webview, localstorage, timezone } + +enum PluginAbilities { authentication } + @freezed class PluginConfiguration with _$PluginConfiguration { const PluginConfiguration._(); @@ -12,6 +16,9 @@ class PluginConfiguration with _$PluginConfiguration { required String description, required String version, required String author, + required String entryPoint, + @Default([]) List apis, + @Default([]) List abilities, }) = _PluginConfiguration; factory PluginConfiguration.fromJson(Map json) => diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart index 3e6197e6..2852c537 100644 --- a/lib/provider/metadata_plugin/metadata_plugin_provider.dart +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -101,6 +101,23 @@ class MetadataPluginNotifier extends AsyncNotifier { author: plugin.author, description: plugin.description, version: plugin.version, + entryPoint: plugin.entryPoint, + apis: plugin.apis + .map( + (e) => PluginApis.values.firstWhereOrNull( + (api) => api.name == e, + ), + ) + .nonNulls + .toList(), + abilities: plugin.abilities + .map( + (e) => PluginAbilities.values.firstWhereOrNull( + (ability) => ability.name == e, + ), + ) + .nonNulls + .toList(), ); }, ).toList(); @@ -269,6 +286,9 @@ class MetadataPluginNotifier extends AsyncNotifier { author: plugin.author, description: plugin.description, version: plugin.version, + entryPoint: plugin.entryPoint, + apis: plugin.apis.map((e) => e.name).toList(), + abilities: plugin.abilities.map((e) => e.name).toList(), ), ); } diff --git a/test/drift/app_db/generated/schema_v7.dart b/test/drift/app_db/generated/schema_v7.dart index dcd2e2a9..49f87c47 100644 --- a/test/drift/app_db/generated/schema_v7.dart +++ b/test/drift/app_db/generated/schema_v7.dart @@ -3450,6 +3450,15 @@ class MetadataPluginsTable extends Table late final GeneratedColumn author = GeneratedColumn( 'author', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn entryPoint = GeneratedColumn( + 'entry_point', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn apis = GeneratedColumn( + 'apis', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn abilities = GeneratedColumn( + 'abilities', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); late final GeneratedColumn selected = GeneratedColumn( 'selected', aliasedName, false, type: DriftSqlType.bool, @@ -3458,8 +3467,17 @@ class MetadataPluginsTable extends Table GeneratedColumn.constraintIsAlways('CHECK ("selected" IN (0, 1))'), defaultValue: const Constant(false)); @override - List get $columns => - [id, name, description, version, author, selected]; + List get $columns => [ + id, + name, + description, + version, + author, + entryPoint, + apis, + abilities, + selected + ]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -3482,6 +3500,12 @@ class MetadataPluginsTable extends Table .read(DriftSqlType.string, data['${effectivePrefix}version'])!, author: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}author'])!, + entryPoint: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}entry_point'])!, + apis: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}apis'])!, + abilities: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}abilities'])!, selected: attachedDatabase.typeMapping .read(DriftSqlType.bool, data['${effectivePrefix}selected'])!, ); @@ -3500,6 +3524,9 @@ class MetadataPluginsTableData extends DataClass final String description; final String version; final String author; + final String entryPoint; + final String apis; + final String abilities; final bool selected; const MetadataPluginsTableData( {required this.id, @@ -3507,6 +3534,9 @@ class MetadataPluginsTableData extends DataClass required this.description, required this.version, required this.author, + required this.entryPoint, + required this.apis, + required this.abilities, required this.selected}); @override Map toColumns(bool nullToAbsent) { @@ -3516,6 +3546,9 @@ class MetadataPluginsTableData extends DataClass map['description'] = Variable(description); map['version'] = Variable(version); map['author'] = Variable(author); + map['entry_point'] = Variable(entryPoint); + map['apis'] = Variable(apis); + map['abilities'] = Variable(abilities); map['selected'] = Variable(selected); return map; } @@ -3527,6 +3560,9 @@ class MetadataPluginsTableData extends DataClass description: Value(description), version: Value(version), author: Value(author), + entryPoint: Value(entryPoint), + apis: Value(apis), + abilities: Value(abilities), selected: Value(selected), ); } @@ -3540,6 +3576,9 @@ class MetadataPluginsTableData extends DataClass description: serializer.fromJson(json['description']), version: serializer.fromJson(json['version']), author: serializer.fromJson(json['author']), + entryPoint: serializer.fromJson(json['entryPoint']), + apis: serializer.fromJson(json['apis']), + abilities: serializer.fromJson(json['abilities']), selected: serializer.fromJson(json['selected']), ); } @@ -3552,6 +3591,9 @@ class MetadataPluginsTableData extends DataClass 'description': serializer.toJson(description), 'version': serializer.toJson(version), 'author': serializer.toJson(author), + 'entryPoint': serializer.toJson(entryPoint), + 'apis': serializer.toJson(apis), + 'abilities': serializer.toJson(abilities), 'selected': serializer.toJson(selected), }; } @@ -3562,6 +3604,9 @@ class MetadataPluginsTableData extends DataClass String? description, String? version, String? author, + String? entryPoint, + String? apis, + String? abilities, bool? selected}) => MetadataPluginsTableData( id: id ?? this.id, @@ -3569,6 +3614,9 @@ class MetadataPluginsTableData extends DataClass description: description ?? this.description, version: version ?? this.version, author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, selected: selected ?? this.selected, ); MetadataPluginsTableData copyWithCompanion( @@ -3580,6 +3628,10 @@ class MetadataPluginsTableData extends DataClass data.description.present ? data.description.value : this.description, version: data.version.present ? data.version.value : this.version, author: data.author.present ? data.author.value : this.author, + entryPoint: + data.entryPoint.present ? data.entryPoint.value : this.entryPoint, + apis: data.apis.present ? data.apis.value : this.apis, + abilities: data.abilities.present ? data.abilities.value : this.abilities, selected: data.selected.present ? data.selected.value : this.selected, ); } @@ -3592,14 +3644,17 @@ class MetadataPluginsTableData extends DataClass ..write('description: $description, ') ..write('version: $version, ') ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') ..write('selected: $selected') ..write(')')) .toString(); } @override - int get hashCode => - Object.hash(id, name, description, version, author, selected); + int get hashCode => Object.hash(id, name, description, version, author, + entryPoint, apis, abilities, selected); @override bool operator ==(Object other) => identical(this, other) || @@ -3609,6 +3664,9 @@ class MetadataPluginsTableData extends DataClass other.description == this.description && other.version == this.version && other.author == this.author && + other.entryPoint == this.entryPoint && + other.apis == this.apis && + other.abilities == this.abilities && other.selected == this.selected); } @@ -3619,6 +3677,9 @@ class MetadataPluginsTableCompanion final Value description; final Value version; final Value author; + final Value entryPoint; + final Value apis; + final Value abilities; final Value selected; const MetadataPluginsTableCompanion({ this.id = const Value.absent(), @@ -3626,6 +3687,9 @@ class MetadataPluginsTableCompanion this.description = const Value.absent(), this.version = const Value.absent(), this.author = const Value.absent(), + this.entryPoint = const Value.absent(), + this.apis = const Value.absent(), + this.abilities = const Value.absent(), this.selected = const Value.absent(), }); MetadataPluginsTableCompanion.insert({ @@ -3634,17 +3698,26 @@ class MetadataPluginsTableCompanion required String description, required String version, required String author, + required String entryPoint, + required String apis, + required String abilities, this.selected = const Value.absent(), }) : name = Value(name), description = Value(description), version = Value(version), - author = Value(author); + author = Value(author), + entryPoint = Value(entryPoint), + apis = Value(apis), + abilities = Value(abilities); static Insertable custom({ Expression? id, Expression? name, Expression? description, Expression? version, Expression? author, + Expression? entryPoint, + Expression? apis, + Expression? abilities, Expression? selected, }) { return RawValuesInsertable({ @@ -3653,6 +3726,9 @@ class MetadataPluginsTableCompanion if (description != null) 'description': description, if (version != null) 'version': version, if (author != null) 'author': author, + if (entryPoint != null) 'entry_point': entryPoint, + if (apis != null) 'apis': apis, + if (abilities != null) 'abilities': abilities, if (selected != null) 'selected': selected, }); } @@ -3663,6 +3739,9 @@ class MetadataPluginsTableCompanion Value? description, Value? version, Value? author, + Value? entryPoint, + Value? apis, + Value? abilities, Value? selected}) { return MetadataPluginsTableCompanion( id: id ?? this.id, @@ -3670,6 +3749,9 @@ class MetadataPluginsTableCompanion description: description ?? this.description, version: version ?? this.version, author: author ?? this.author, + entryPoint: entryPoint ?? this.entryPoint, + apis: apis ?? this.apis, + abilities: abilities ?? this.abilities, selected: selected ?? this.selected, ); } @@ -3692,6 +3774,15 @@ class MetadataPluginsTableCompanion if (author.present) { map['author'] = Variable(author.value); } + if (entryPoint.present) { + map['entry_point'] = Variable(entryPoint.value); + } + if (apis.present) { + map['apis'] = Variable(apis.value); + } + if (abilities.present) { + map['abilities'] = Variable(abilities.value); + } if (selected.present) { map['selected'] = Variable(selected.value); } @@ -3706,6 +3797,9 @@ class MetadataPluginsTableCompanion ..write('description: $description, ') ..write('version: $version, ') ..write('author: $author, ') + ..write('entryPoint: $entryPoint, ') + ..write('apis: $apis, ') + ..write('abilities: $abilities, ') ..write('selected: $selected') ..write(')')) .toString(); From 7a6821f28da0daa80f6da6aef0de1e586c3ca0a2 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 08:41:38 +0600 Subject: [PATCH 10/60] feat: implement metadata plugins based on hetu --- lib/collections/routes.dart | 4 - lib/collections/routes.gr.dart | 400 +++++------- lib/main.dart | 2 +- lib/models/metadata/image.dart | 6 +- lib/models/metadata/metadata.freezed.dart | 180 +++--- lib/models/metadata/metadata.g.dart | 18 +- lib/models/metadata/user.dart | 7 +- lib/pages/settings/metadata_plugins.dart | 18 +- lib/pages/webview/webview.dart | 56 -- lib/provider/metadata_plugin/auth.dart | 56 +- .../metadata_plugin_provider.dart | 46 +- lib/provider/metadata_plugin/user.dart | 16 + lib/services/metadata/apis/localstorage.dart | 110 ++-- lib/services/metadata/apis/set_interval.dart | 80 --- lib/services/metadata/apis/totp.dart | 40 -- lib/services/metadata/apis/webview.dart | 171 ----- lib/services/metadata/endpoints/album.dart | 0 lib/services/metadata/endpoints/artist.dart | 0 lib/services/metadata/endpoints/auth.dart | 33 + lib/services/metadata/endpoints/browse.dart | 0 lib/services/metadata/endpoints/playlist.dart | 0 lib/services/metadata/endpoints/search.dart | 0 lib/services/metadata/endpoints/track.dart | 0 lib/services/metadata/endpoints/user.dart | 15 + lib/services/metadata/metadata.dart | 599 ++---------------- linux/flutter/generated_plugin_registrant.cc | 8 +- linux/flutter/generated_plugins.cmake | 2 +- macos/Flutter/GeneratedPluginRegistrant.swift | 4 +- pubspec.lock | 73 ++- pubspec.yaml | 22 +- .../flutter/generated_plugin_registrant.cc | 6 +- windows/flutter/generated_plugins.cmake | 2 +- 32 files changed, 577 insertions(+), 1397 deletions(-) delete mode 100644 lib/pages/webview/webview.dart create mode 100644 lib/provider/metadata_plugin/user.dart delete mode 100644 lib/services/metadata/apis/set_interval.dart delete mode 100644 lib/services/metadata/apis/totp.dart delete mode 100644 lib/services/metadata/apis/webview.dart create mode 100644 lib/services/metadata/endpoints/album.dart create mode 100644 lib/services/metadata/endpoints/artist.dart create mode 100644 lib/services/metadata/endpoints/auth.dart create mode 100644 lib/services/metadata/endpoints/browse.dart create mode 100644 lib/services/metadata/endpoints/playlist.dart create mode 100644 lib/services/metadata/endpoints/search.dart create mode 100644 lib/services/metadata/endpoints/track.dart create mode 100644 lib/services/metadata/endpoints/user.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 3f2043a1..835260bc 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -235,9 +235,5 @@ class AppRouter extends RootStackRouter { page: LastFMLoginRoute.page, // parentNavigatorKey: rootNavigatorKey, ), - AutoRoute( - path: "/webview", - page: WebviewRoute.page, - ), ]; } diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index a049fd28..291254d7 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,12 +8,11 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i45; -import 'package:flutter/material.dart' as _i46; -import 'package:flutter_inappwebview/flutter_inappwebview.dart' as _i51; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48; -import 'package:spotify/spotify.dart' as _i47; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i49; +import 'package:auto_route/auto_route.dart' as _i44; +import 'package:flutter/material.dart' as _i45; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; +import 'package:spotify/spotify.dart' as _i46; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -61,13 +60,11 @@ import 'package:spotube/pages/stats/playlists/playlists.dart' as _i34; import 'package:spotube/pages/stats/stats.dart' as _i33; import 'package:spotube/pages/stats/streams/streams.dart' as _i36; import 'package:spotube/pages/track/track.dart' as _i37; -import 'package:spotube/pages/webview/webview.dart' as _i44; -import 'package:spotube/services/metadata/apis/webview.dart' as _i50; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i45.PageRouteInfo { - const AboutSpotubeRoute({List<_i45.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i44.PageRouteInfo { + const AboutSpotubeRoute({List<_i44.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -75,7 +72,7 @@ class AboutSpotubeRoute extends _i45.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -85,12 +82,12 @@ class AboutSpotubeRoute extends _i45.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i45.PageRouteInfo { +class AlbumRoute extends _i44.PageRouteInfo { AlbumRoute({ - _i46.Key? key, + _i45.Key? key, required String id, - required _i47.AlbumSimple album, - List<_i45.PageRouteInfo>? children, + required _i46.AlbumSimple album, + List<_i44.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -104,7 +101,7 @@ class AlbumRoute extends _i45.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -124,11 +121,11 @@ class AlbumRouteArgs { required this.album, }); - final _i46.Key? key; + final _i45.Key? key; final String id; - final _i47.AlbumSimple album; + final _i46.AlbumSimple album; @override String toString() { @@ -138,11 +135,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i45.PageRouteInfo { +class ArtistRoute extends _i44.PageRouteInfo { ArtistRoute({ required String artistId, - _i46.Key? key, - List<_i45.PageRouteInfo>? children, + _i45.Key? key, + List<_i44.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -155,7 +152,7 @@ class ArtistRoute extends _i45.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -177,7 +174,7 @@ class ArtistRouteArgs { final String artistId; - final _i46.Key? key; + final _i45.Key? key; @override String toString() { @@ -187,8 +184,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i45.PageRouteInfo { - const BlackListRoute({List<_i45.PageRouteInfo>? children}) +class BlackListRoute extends _i44.PageRouteInfo { + const BlackListRoute({List<_i44.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -196,7 +193,7 @@ class BlackListRoute extends _i45.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -206,8 +203,8 @@ class BlackListRoute extends _i45.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i45.PageRouteInfo { - const ConnectControlRoute({List<_i45.PageRouteInfo>? children}) +class ConnectControlRoute extends _i44.PageRouteInfo { + const ConnectControlRoute({List<_i44.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -215,7 +212,7 @@ class ConnectControlRoute extends _i45.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -225,8 +222,8 @@ class ConnectControlRoute extends _i45.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i45.PageRouteInfo { - const ConnectRoute({List<_i45.PageRouteInfo>? children}) +class ConnectRoute extends _i44.PageRouteInfo { + const ConnectRoute({List<_i44.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -234,7 +231,7 @@ class ConnectRoute extends _i45.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -244,8 +241,8 @@ class ConnectRoute extends _i45.PageRouteInfo { /// generated route for /// [_i7.GenrePage] -class GenreRoute extends _i45.PageRouteInfo { - const GenreRoute({List<_i45.PageRouteInfo>? children}) +class GenreRoute extends _i44.PageRouteInfo { + const GenreRoute({List<_i44.PageRouteInfo>? children}) : super( GenreRoute.name, initialChildren: children, @@ -253,7 +250,7 @@ class GenreRoute extends _i45.PageRouteInfo { static const String name = 'GenreRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i7.GenrePage(); @@ -263,12 +260,12 @@ class GenreRoute extends _i45.PageRouteInfo { /// generated route for /// [_i8.GenrePlaylistsPage] -class GenrePlaylistsRoute extends _i45.PageRouteInfo { +class GenrePlaylistsRoute extends _i44.PageRouteInfo { GenrePlaylistsRoute({ - _i46.Key? key, + _i45.Key? key, required String id, - required _i47.Category category, - List<_i45.PageRouteInfo>? children, + required _i46.Category category, + List<_i44.PageRouteInfo>? children, }) : super( GenrePlaylistsRoute.name, args: GenrePlaylistsRouteArgs( @@ -282,7 +279,7 @@ class GenrePlaylistsRoute extends _i45.PageRouteInfo { static const String name = 'GenrePlaylistsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -302,11 +299,11 @@ class GenrePlaylistsRouteArgs { required this.category, }); - final _i46.Key? key; + final _i45.Key? key; final String id; - final _i47.Category category; + final _i46.Category category; @override String toString() { @@ -316,8 +313,8 @@ class GenrePlaylistsRouteArgs { /// generated route for /// [_i9.GettingStartedPage] -class GettingStartedRoute extends _i45.PageRouteInfo { - const GettingStartedRoute({List<_i45.PageRouteInfo>? children}) +class GettingStartedRoute extends _i44.PageRouteInfo { + const GettingStartedRoute({List<_i44.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -325,7 +322,7 @@ class GettingStartedRoute extends _i45.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i9.GettingStartedPage(); @@ -336,11 +333,11 @@ class GettingStartedRoute extends _i45.PageRouteInfo { /// generated route for /// [_i10.HomeFeedSectionPage] class HomeFeedSectionRoute - extends _i45.PageRouteInfo { + extends _i44.PageRouteInfo { HomeFeedSectionRoute({ - _i48.Key? key, + _i47.Key? key, required String sectionUri, - List<_i45.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( HomeFeedSectionRoute.name, args: HomeFeedSectionRouteArgs( @@ -353,7 +350,7 @@ class HomeFeedSectionRoute static const String name = 'HomeFeedSectionRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -374,7 +371,7 @@ class HomeFeedSectionRouteArgs { required this.sectionUri, }); - final _i48.Key? key; + final _i47.Key? key; final String sectionUri; @@ -386,8 +383,8 @@ class HomeFeedSectionRouteArgs { /// generated route for /// [_i11.HomePage] -class HomeRoute extends _i45.PageRouteInfo { - const HomeRoute({List<_i45.PageRouteInfo>? children}) +class HomeRoute extends _i44.PageRouteInfo { + const HomeRoute({List<_i44.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -395,7 +392,7 @@ class HomeRoute extends _i45.PageRouteInfo { static const String name = 'HomeRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i11.HomePage(); @@ -405,8 +402,8 @@ class HomeRoute extends _i45.PageRouteInfo { /// generated route for /// [_i12.LastFMLoginPage] -class LastFMLoginRoute extends _i45.PageRouteInfo { - const LastFMLoginRoute({List<_i45.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i44.PageRouteInfo { + const LastFMLoginRoute({List<_i44.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -414,7 +411,7 @@ class LastFMLoginRoute extends _i45.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i12.LastFMLoginPage(); @@ -424,8 +421,8 @@ class LastFMLoginRoute extends _i45.PageRouteInfo { /// generated route for /// [_i13.LibraryPage] -class LibraryRoute extends _i45.PageRouteInfo { - const LibraryRoute({List<_i45.PageRouteInfo>? children}) +class LibraryRoute extends _i44.PageRouteInfo { + const LibraryRoute({List<_i44.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -433,7 +430,7 @@ class LibraryRoute extends _i45.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i13.LibraryPage(); @@ -443,11 +440,11 @@ class LibraryRoute extends _i45.PageRouteInfo { /// generated route for /// [_i14.LikedPlaylistPage] -class LikedPlaylistRoute extends _i45.PageRouteInfo { +class LikedPlaylistRoute extends _i44.PageRouteInfo { LikedPlaylistRoute({ - _i46.Key? key, - required _i47.PlaylistSimple playlist, - List<_i45.PageRouteInfo>? children, + _i45.Key? key, + required _i46.PlaylistSimple playlist, + List<_i44.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -459,7 +456,7 @@ class LikedPlaylistRoute extends _i45.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -477,9 +474,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i46.Key? key; + final _i45.Key? key; - final _i47.PlaylistSimple playlist; + final _i46.PlaylistSimple playlist; @override String toString() { @@ -489,13 +486,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i15.LocalLibraryPage] -class LocalLibraryRoute extends _i45.PageRouteInfo { +class LocalLibraryRoute extends _i44.PageRouteInfo { LocalLibraryRoute({ required String location, - _i46.Key? key, + _i45.Key? key, bool isDownloads = false, bool isCache = false, - List<_i45.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -509,7 +506,7 @@ class LocalLibraryRoute extends _i45.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -533,7 +530,7 @@ class LocalLibraryRouteArgs { final String location; - final _i46.Key? key; + final _i45.Key? key; final bool isDownloads; @@ -547,8 +544,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i16.LogsPage] -class LogsRoute extends _i45.PageRouteInfo { - const LogsRoute({List<_i45.PageRouteInfo>? children}) +class LogsRoute extends _i44.PageRouteInfo { + const LogsRoute({List<_i44.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -556,7 +553,7 @@ class LogsRoute extends _i45.PageRouteInfo { static const String name = 'LogsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i16.LogsPage(); @@ -566,8 +563,8 @@ class LogsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i17.LyricsPage] -class LyricsRoute extends _i45.PageRouteInfo { - const LyricsRoute({List<_i45.PageRouteInfo>? children}) +class LyricsRoute extends _i44.PageRouteInfo { + const LyricsRoute({List<_i44.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -575,7 +572,7 @@ class LyricsRoute extends _i45.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i17.LyricsPage(); @@ -585,11 +582,11 @@ class LyricsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i18.MiniLyricsPage] -class MiniLyricsRoute extends _i45.PageRouteInfo { +class MiniLyricsRoute extends _i44.PageRouteInfo { MiniLyricsRoute({ - _i48.Key? key, - required _i48.Size prevSize, - List<_i45.PageRouteInfo>? children, + _i47.Key? key, + required _i47.Size prevSize, + List<_i44.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -601,7 +598,7 @@ class MiniLyricsRoute extends _i45.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -619,9 +616,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i48.Key? key; + final _i47.Key? key; - final _i48.Size prevSize; + final _i47.Size prevSize; @override String toString() { @@ -631,8 +628,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i19.PlayerLyricsPage] -class PlayerLyricsRoute extends _i45.PageRouteInfo { - const PlayerLyricsRoute({List<_i45.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i44.PageRouteInfo { + const PlayerLyricsRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -640,7 +637,7 @@ class PlayerLyricsRoute extends _i45.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i19.PlayerLyricsPage(); @@ -650,8 +647,8 @@ class PlayerLyricsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i20.PlayerQueuePage] -class PlayerQueueRoute extends _i45.PageRouteInfo { - const PlayerQueueRoute({List<_i45.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i44.PageRouteInfo { + const PlayerQueueRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -659,7 +656,7 @@ class PlayerQueueRoute extends _i45.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i20.PlayerQueuePage(); @@ -669,8 +666,8 @@ class PlayerQueueRoute extends _i45.PageRouteInfo { /// generated route for /// [_i21.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i45.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i45.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i44.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -678,7 +675,7 @@ class PlayerTrackSourcesRoute extends _i45.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i21.PlayerTrackSourcesPage(); @@ -689,11 +686,11 @@ class PlayerTrackSourcesRoute extends _i45.PageRouteInfo { /// generated route for /// [_i22.PlaylistGenerateResultPage] class PlaylistGenerateResultRoute - extends _i45.PageRouteInfo { + extends _i44.PageRouteInfo { PlaylistGenerateResultRoute({ - _i48.Key? key, - required _i49.GeneratePlaylistProviderInput state, - List<_i45.PageRouteInfo>? children, + _i47.Key? key, + required _i48.GeneratePlaylistProviderInput state, + List<_i44.PageRouteInfo>? children, }) : super( PlaylistGenerateResultRoute.name, args: PlaylistGenerateResultRouteArgs( @@ -705,7 +702,7 @@ class PlaylistGenerateResultRoute static const String name = 'PlaylistGenerateResultRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -723,9 +720,9 @@ class PlaylistGenerateResultRouteArgs { required this.state, }); - final _i48.Key? key; + final _i47.Key? key; - final _i49.GeneratePlaylistProviderInput state; + final _i48.GeneratePlaylistProviderInput state; @override String toString() { @@ -735,8 +732,8 @@ class PlaylistGenerateResultRouteArgs { /// generated route for /// [_i23.PlaylistGeneratorPage] -class PlaylistGeneratorRoute extends _i45.PageRouteInfo { - const PlaylistGeneratorRoute({List<_i45.PageRouteInfo>? children}) +class PlaylistGeneratorRoute extends _i44.PageRouteInfo { + const PlaylistGeneratorRoute({List<_i44.PageRouteInfo>? children}) : super( PlaylistGeneratorRoute.name, initialChildren: children, @@ -744,7 +741,7 @@ class PlaylistGeneratorRoute extends _i45.PageRouteInfo { static const String name = 'PlaylistGeneratorRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i23.PlaylistGeneratorPage(); @@ -754,12 +751,12 @@ class PlaylistGeneratorRoute extends _i45.PageRouteInfo { /// generated route for /// [_i24.PlaylistPage] -class PlaylistRoute extends _i45.PageRouteInfo { +class PlaylistRoute extends _i44.PageRouteInfo { PlaylistRoute({ - _i46.Key? key, + _i45.Key? key, required String id, - required _i47.PlaylistSimple playlist, - List<_i45.PageRouteInfo>? children, + required _i46.PlaylistSimple playlist, + List<_i44.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -773,7 +770,7 @@ class PlaylistRoute extends _i45.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -793,11 +790,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i46.Key? key; + final _i45.Key? key; final String id; - final _i47.PlaylistSimple playlist; + final _i46.PlaylistSimple playlist; @override String toString() { @@ -807,8 +804,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i25.ProfilePage] -class ProfileRoute extends _i45.PageRouteInfo { - const ProfileRoute({List<_i45.PageRouteInfo>? children}) +class ProfileRoute extends _i44.PageRouteInfo { + const ProfileRoute({List<_i44.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -816,7 +813,7 @@ class ProfileRoute extends _i45.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i25.ProfilePage(); @@ -826,8 +823,8 @@ class ProfileRoute extends _i45.PageRouteInfo { /// generated route for /// [_i26.RootAppPage] -class RootAppRoute extends _i45.PageRouteInfo { - const RootAppRoute({List<_i45.PageRouteInfo>? children}) +class RootAppRoute extends _i44.PageRouteInfo { + const RootAppRoute({List<_i44.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -835,7 +832,7 @@ class RootAppRoute extends _i45.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i26.RootAppPage(); @@ -845,8 +842,8 @@ class RootAppRoute extends _i45.PageRouteInfo { /// generated route for /// [_i27.SearchPage] -class SearchRoute extends _i45.PageRouteInfo { - const SearchRoute({List<_i45.PageRouteInfo>? children}) +class SearchRoute extends _i44.PageRouteInfo { + const SearchRoute({List<_i44.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -854,7 +851,7 @@ class SearchRoute extends _i45.PageRouteInfo { static const String name = 'SearchRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i27.SearchPage(); @@ -864,8 +861,8 @@ class SearchRoute extends _i45.PageRouteInfo { /// generated route for /// [_i28.SettingsMetadataProviderPage] -class SettingsMetadataProviderRoute extends _i45.PageRouteInfo { - const SettingsMetadataProviderRoute({List<_i45.PageRouteInfo>? children}) +class SettingsMetadataProviderRoute extends _i44.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i44.PageRouteInfo>? children}) : super( SettingsMetadataProviderRoute.name, initialChildren: children, @@ -873,7 +870,7 @@ class SettingsMetadataProviderRoute extends _i45.PageRouteInfo { static const String name = 'SettingsMetadataProviderRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i28.SettingsMetadataProviderPage(); @@ -883,8 +880,8 @@ class SettingsMetadataProviderRoute extends _i45.PageRouteInfo { /// generated route for /// [_i29.SettingsPage] -class SettingsRoute extends _i45.PageRouteInfo { - const SettingsRoute({List<_i45.PageRouteInfo>? children}) +class SettingsRoute extends _i44.PageRouteInfo { + const SettingsRoute({List<_i44.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -892,7 +889,7 @@ class SettingsRoute extends _i45.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i29.SettingsPage(); @@ -902,8 +899,8 @@ class SettingsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i30.StatsAlbumsPage] -class StatsAlbumsRoute extends _i45.PageRouteInfo { - const StatsAlbumsRoute({List<_i45.PageRouteInfo>? children}) +class StatsAlbumsRoute extends _i44.PageRouteInfo { + const StatsAlbumsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -911,7 +908,7 @@ class StatsAlbumsRoute extends _i45.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i30.StatsAlbumsPage(); @@ -921,8 +918,8 @@ class StatsAlbumsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i31.StatsArtistsPage] -class StatsArtistsRoute extends _i45.PageRouteInfo { - const StatsArtistsRoute({List<_i45.PageRouteInfo>? children}) +class StatsArtistsRoute extends _i44.PageRouteInfo { + const StatsArtistsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -930,7 +927,7 @@ class StatsArtistsRoute extends _i45.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i31.StatsArtistsPage(); @@ -940,8 +937,8 @@ class StatsArtistsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i32.StatsMinutesPage] -class StatsMinutesRoute extends _i45.PageRouteInfo { - const StatsMinutesRoute({List<_i45.PageRouteInfo>? children}) +class StatsMinutesRoute extends _i44.PageRouteInfo { + const StatsMinutesRoute({List<_i44.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -949,7 +946,7 @@ class StatsMinutesRoute extends _i45.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i32.StatsMinutesPage(); @@ -959,8 +956,8 @@ class StatsMinutesRoute extends _i45.PageRouteInfo { /// generated route for /// [_i33.StatsPage] -class StatsRoute extends _i45.PageRouteInfo { - const StatsRoute({List<_i45.PageRouteInfo>? children}) +class StatsRoute extends _i44.PageRouteInfo { + const StatsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -968,7 +965,7 @@ class StatsRoute extends _i45.PageRouteInfo { static const String name = 'StatsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i33.StatsPage(); @@ -978,8 +975,8 @@ class StatsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i34.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i45.PageRouteInfo { - const StatsPlaylistsRoute({List<_i45.PageRouteInfo>? children}) +class StatsPlaylistsRoute extends _i44.PageRouteInfo { + const StatsPlaylistsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -987,7 +984,7 @@ class StatsPlaylistsRoute extends _i45.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i34.StatsPlaylistsPage(); @@ -997,8 +994,8 @@ class StatsPlaylistsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i35.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i45.PageRouteInfo { - const StatsStreamFeesRoute({List<_i45.PageRouteInfo>? children}) +class StatsStreamFeesRoute extends _i44.PageRouteInfo { + const StatsStreamFeesRoute({List<_i44.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -1006,7 +1003,7 @@ class StatsStreamFeesRoute extends _i45.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i35.StatsStreamFeesPage(); @@ -1016,8 +1013,8 @@ class StatsStreamFeesRoute extends _i45.PageRouteInfo { /// generated route for /// [_i36.StatsStreamsPage] -class StatsStreamsRoute extends _i45.PageRouteInfo { - const StatsStreamsRoute({List<_i45.PageRouteInfo>? children}) +class StatsStreamsRoute extends _i44.PageRouteInfo { + const StatsStreamsRoute({List<_i44.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -1025,7 +1022,7 @@ class StatsStreamsRoute extends _i45.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i36.StatsStreamsPage(); @@ -1035,11 +1032,11 @@ class StatsStreamsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i37.TrackPage] -class TrackRoute extends _i45.PageRouteInfo { +class TrackRoute extends _i44.PageRouteInfo { TrackRoute({ - _i48.Key? key, + _i47.Key? key, required String trackId, - List<_i45.PageRouteInfo>? children, + List<_i44.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -1052,7 +1049,7 @@ class TrackRoute extends _i45.PageRouteInfo { static const String name = 'TrackRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -1072,7 +1069,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i48.Key? key; + final _i47.Key? key; final String trackId; @@ -1084,8 +1081,8 @@ class TrackRouteArgs { /// generated route for /// [_i38.UserAlbumsPage] -class UserAlbumsRoute extends _i45.PageRouteInfo { - const UserAlbumsRoute({List<_i45.PageRouteInfo>? children}) +class UserAlbumsRoute extends _i44.PageRouteInfo { + const UserAlbumsRoute({List<_i44.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -1093,7 +1090,7 @@ class UserAlbumsRoute extends _i45.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i38.UserAlbumsPage(); @@ -1103,8 +1100,8 @@ class UserAlbumsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i39.UserArtistsPage] -class UserArtistsRoute extends _i45.PageRouteInfo { - const UserArtistsRoute({List<_i45.PageRouteInfo>? children}) +class UserArtistsRoute extends _i44.PageRouteInfo { + const UserArtistsRoute({List<_i44.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -1112,7 +1109,7 @@ class UserArtistsRoute extends _i45.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i39.UserArtistsPage(); @@ -1122,8 +1119,8 @@ class UserArtistsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i40.UserDownloadsPage] -class UserDownloadsRoute extends _i45.PageRouteInfo { - const UserDownloadsRoute({List<_i45.PageRouteInfo>? children}) +class UserDownloadsRoute extends _i44.PageRouteInfo { + const UserDownloadsRoute({List<_i44.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -1131,7 +1128,7 @@ class UserDownloadsRoute extends _i45.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i40.UserDownloadsPage(); @@ -1141,8 +1138,8 @@ class UserDownloadsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i41.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i45.PageRouteInfo { - const UserLocalLibraryRoute({List<_i45.PageRouteInfo>? children}) +class UserLocalLibraryRoute extends _i44.PageRouteInfo { + const UserLocalLibraryRoute({List<_i44.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1150,7 +1147,7 @@ class UserLocalLibraryRoute extends _i45.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i41.UserLocalLibraryPage(); @@ -1160,8 +1157,8 @@ class UserLocalLibraryRoute extends _i45.PageRouteInfo { /// generated route for /// [_i42.UserPlaylistsPage] -class UserPlaylistsRoute extends _i45.PageRouteInfo { - const UserPlaylistsRoute({List<_i45.PageRouteInfo>? children}) +class UserPlaylistsRoute extends _i44.PageRouteInfo { + const UserPlaylistsRoute({List<_i44.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1169,7 +1166,7 @@ class UserPlaylistsRoute extends _i45.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i42.UserPlaylistsPage(); @@ -1179,8 +1176,8 @@ class UserPlaylistsRoute extends _i45.PageRouteInfo { /// generated route for /// [_i43.WebViewLoginPage] -class WebViewLoginRoute extends _i45.PageRouteInfo { - const WebViewLoginRoute({List<_i45.PageRouteInfo>? children}) +class WebViewLoginRoute extends _i44.PageRouteInfo { + const WebViewLoginRoute({List<_i44.PageRouteInfo>? children}) : super( WebViewLoginRoute.name, initialChildren: children, @@ -1188,75 +1185,10 @@ class WebViewLoginRoute extends _i45.PageRouteInfo { static const String name = 'WebViewLoginRoute'; - static _i45.PageInfo page = _i45.PageInfo( + static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { return const _i43.WebViewLoginPage(); }, ); } - -/// generated route for -/// [_i44.WebviewPage] -class WebviewRoute extends _i45.PageRouteInfo { - WebviewRoute({ - _i48.Key? key, - _i50.WebviewInitialSettings? initialSettings, - String? url, - void Function( - _i51.InAppWebViewController, - _i51.WebUri?, - )? onLoadStop, - List<_i45.PageRouteInfo>? children, - }) : super( - WebviewRoute.name, - args: WebviewRouteArgs( - key: key, - initialSettings: initialSettings, - url: url, - onLoadStop: onLoadStop, - ), - initialChildren: children, - ); - - static const String name = 'WebviewRoute'; - - static _i45.PageInfo page = _i45.PageInfo( - name, - builder: (data) { - final args = - data.argsAs(orElse: () => const WebviewRouteArgs()); - return _i44.WebviewPage( - key: args.key, - initialSettings: args.initialSettings, - url: args.url, - onLoadStop: args.onLoadStop, - ); - }, - ); -} - -class WebviewRouteArgs { - const WebviewRouteArgs({ - this.key, - this.initialSettings, - this.url, - this.onLoadStop, - }); - - final _i48.Key? key; - - final _i50.WebviewInitialSettings? initialSettings; - - final String? url; - - final void Function( - _i51.InAppWebViewController, - _i51.WebUri?, - )? onLoadStop; - - @override - String toString() { - return 'WebviewRouteArgs{key: $key, initialSettings: $initialSettings, url: $url, onLoadStop: $onLoadStop}'; - } -} diff --git a/lib/main.dart b/lib/main.dart index 3e9edbcd..6a619ad7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -147,7 +147,7 @@ class Spotube extends HookConsumerWidget { ref.listen(bonsoirProvider, (_, __) {}); ref.listen(connectClientsProvider, (_, __) {}); ref.listen(metadataPluginsProvider, (_, __) {}); - ref.listen(metadataPluginApiProvider, (_, __) {}); + ref.listen(metadataPluginProvider, (_, __) {}); ref.listen(serverProvider, (_, __) {}); ref.listen(trayManagerProvider, (_, __) {}); diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index aa20c385..1fc3bb30 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -3,9 +3,9 @@ part of 'metadata.dart'; @freezed class SpotubeImageObject with _$SpotubeImageObject { factory SpotubeImageObject({ - required final String url, - required final int width, - required final int height, + required String url, + int? width, + int? height, }) = _SpotubeImageObject; factory SpotubeImageObject.fromJson(Map json) => diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index f5544cb5..7f420732 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -775,8 +775,8 @@ SpotubeImageObject _$SpotubeImageObjectFromJson(Map json) { /// @nodoc mixin _$SpotubeImageObject { String get url => throw _privateConstructorUsedError; - int get width => throw _privateConstructorUsedError; - int get height => throw _privateConstructorUsedError; + int? get width => throw _privateConstructorUsedError; + int? get height => throw _privateConstructorUsedError; /// Serializes this SpotubeImageObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -794,7 +794,7 @@ abstract class $SpotubeImageObjectCopyWith<$Res> { SpotubeImageObject value, $Res Function(SpotubeImageObject) then) = _$SpotubeImageObjectCopyWithImpl<$Res, SpotubeImageObject>; @useResult - $Res call({String url, int width, int height}); + $Res call({String url, int? width, int? height}); } /// @nodoc @@ -813,22 +813,22 @@ class _$SpotubeImageObjectCopyWithImpl<$Res, $Val extends SpotubeImageObject> @override $Res call({ Object? url = null, - Object? width = null, - Object? height = null, + Object? width = freezed, + Object? height = freezed, }) { return _then(_value.copyWith( url: null == url ? _value.url : url // ignore: cast_nullable_to_non_nullable as String, - width: null == width + width: freezed == width ? _value.width : width // ignore: cast_nullable_to_non_nullable - as int, - height: null == height + as int?, + height: freezed == height ? _value.height : height // ignore: cast_nullable_to_non_nullable - as int, + as int?, ) as $Val); } } @@ -841,7 +841,7 @@ abstract class _$$SpotubeImageObjectImplCopyWith<$Res> __$$SpotubeImageObjectImplCopyWithImpl<$Res>; @override @useResult - $Res call({String url, int width, int height}); + $Res call({String url, int? width, int? height}); } /// @nodoc @@ -858,22 +858,22 @@ class __$$SpotubeImageObjectImplCopyWithImpl<$Res> @override $Res call({ Object? url = null, - Object? width = null, - Object? height = null, + Object? width = freezed, + Object? height = freezed, }) { return _then(_$SpotubeImageObjectImpl( url: null == url ? _value.url : url // ignore: cast_nullable_to_non_nullable as String, - width: null == width + width: freezed == width ? _value.width : width // ignore: cast_nullable_to_non_nullable - as int, - height: null == height + as int?, + height: freezed == height ? _value.height : height // ignore: cast_nullable_to_non_nullable - as int, + as int?, )); } } @@ -881,8 +881,7 @@ class __$$SpotubeImageObjectImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() class _$SpotubeImageObjectImpl implements _SpotubeImageObject { - _$SpotubeImageObjectImpl( - {required this.url, required this.width, required this.height}); + _$SpotubeImageObjectImpl({required this.url, this.width, this.height}); factory _$SpotubeImageObjectImpl.fromJson(Map json) => _$$SpotubeImageObjectImplFromJson(json); @@ -890,9 +889,9 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject { @override final String url; @override - final int width; + final int? width; @override - final int height; + final int? height; @override String toString() { @@ -933,8 +932,8 @@ class _$SpotubeImageObjectImpl implements _SpotubeImageObject { abstract class _SpotubeImageObject implements SpotubeImageObject { factory _SpotubeImageObject( {required final String url, - required final int width, - required final int height}) = _$SpotubeImageObjectImpl; + final int? width, + final int? height}) = _$SpotubeImageObjectImpl; factory _SpotubeImageObject.fromJson(Map json) = _$SpotubeImageObjectImpl.fromJson; @@ -942,9 +941,9 @@ abstract class _SpotubeImageObject implements SpotubeImageObject { @override String get url; @override - int get width; + int? get width; @override - int get height; + int? get height; /// Create a copy of SpotubeImageObject /// with the given fields replaced by the non-null parameter values. @@ -2192,11 +2191,10 @@ SpotubeUserObject _$SpotubeUserObjectFromJson(Map json) { /// @nodoc mixin _$SpotubeUserObject { - String get uid => throw _privateConstructorUsedError; + String get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; - List get avatars => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; - String get displayName => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; /// Serializes this SpotubeUserObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -2215,11 +2213,10 @@ abstract class $SpotubeUserObjectCopyWith<$Res> { _$SpotubeUserObjectCopyWithImpl<$Res, SpotubeUserObject>; @useResult $Res call( - {String uid, + {String id, String name, - List avatars, - String externalUrl, - String displayName}); + List images, + String externalUri}); } /// @nodoc @@ -2237,32 +2234,27 @@ class _$SpotubeUserObjectCopyWithImpl<$Res, $Val extends SpotubeUserObject> @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? avatars = null, - Object? externalUrl = null, - Object? displayName = null, + Object? images = null, + Object? externalUri = null, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + 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, - avatars: null == avatars - ? _value.avatars - : avatars // ignore: cast_nullable_to_non_nullable + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable as List, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable - as String, - displayName: null == displayName - ? _value.displayName - : displayName // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, ) as $Val); } @@ -2277,11 +2269,10 @@ abstract class _$$SpotubeUserObjectImplCopyWith<$Res> @override @useResult $Res call( - {String uid, + {String id, String name, - List avatars, - String externalUrl, - String displayName}); + List images, + String externalUri}); } /// @nodoc @@ -2297,32 +2288,27 @@ class __$$SpotubeUserObjectImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? avatars = null, - Object? externalUrl = null, - Object? displayName = null, + Object? images = null, + Object? externalUri = null, }) { return _then(_$SpotubeUserObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + 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, - avatars: null == avatars - ? _value._avatars - : avatars // ignore: cast_nullable_to_non_nullable + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable as List, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable - as String, - displayName: null == displayName - ? _value.displayName - : displayName // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, )); } @@ -2332,37 +2318,34 @@ class __$$SpotubeUserObjectImplCopyWithImpl<$Res> @JsonSerializable() class _$SpotubeUserObjectImpl implements _SpotubeUserObject { _$SpotubeUserObjectImpl( - {required this.uid, + {required this.id, required this.name, - final List avatars = const [], - required this.externalUrl, - required this.displayName}) - : _avatars = avatars; + final List images = const [], + required this.externalUri}) + : _images = images; factory _$SpotubeUserObjectImpl.fromJson(Map json) => _$$SpotubeUserObjectImplFromJson(json); @override - final String uid; + final String id; @override final String name; - final List _avatars; + final List _images; @override @JsonKey() - List get avatars { - if (_avatars is EqualUnmodifiableListView) return _avatars; + List get images { + if (_images is EqualUnmodifiableListView) return _images; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_avatars); + return EqualUnmodifiableListView(_images); } @override - final String externalUrl; - @override - final String displayName; + final String externalUri; @override String toString() { - return 'SpotubeUserObject(uid: $uid, name: $name, avatars: $avatars, externalUrl: $externalUrl, displayName: $displayName)'; + return 'SpotubeUserObject(id: $id, name: $name, images: $images, externalUri: $externalUri)'; } @override @@ -2370,19 +2353,17 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject { return identical(this, other) || (other.runtimeType == runtimeType && other is _$SpotubeUserObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && + (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && - const DeepCollectionEquality().equals(other._avatars, _avatars) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl) && - (identical(other.displayName, displayName) || - other.displayName == displayName)); + const DeepCollectionEquality().equals(other._images, _images) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, uid, name, - const DeepCollectionEquality().hash(_avatars), externalUrl, displayName); + int get hashCode => Object.hash(runtimeType, id, name, + const DeepCollectionEquality().hash(_images), externalUri); /// Create a copy of SpotubeUserObject /// with the given fields replaced by the non-null parameter values. @@ -2403,25 +2384,22 @@ class _$SpotubeUserObjectImpl implements _SpotubeUserObject { abstract class _SpotubeUserObject implements SpotubeUserObject { factory _SpotubeUserObject( - {required final String uid, + {required final String id, required final String name, - final List avatars, - required final String externalUrl, - required final String displayName}) = _$SpotubeUserObjectImpl; + final List images, + required final String externalUri}) = _$SpotubeUserObjectImpl; factory _SpotubeUserObject.fromJson(Map json) = _$SpotubeUserObjectImpl.fromJson; @override - String get uid; + String get id; @override String get name; @override - List get avatars; + List get images; @override - String get externalUrl; - @override - String get displayName; + String get externalUri; /// Create a copy of SpotubeUserObject /// 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 7ae2cc75..216cf366 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -84,8 +84,8 @@ Map _$$SpotubeFeedObjectImplToJson( _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => _$SpotubeImageObjectImpl( url: json['url'] as String, - width: (json['width'] as num).toInt(), - height: (json['height'] as num).toInt(), + width: (json['width'] as num?)?.toInt(), + height: (json['height'] as num?)?.toInt(), ); Map _$$SpotubeImageObjectImplToJson( @@ -203,25 +203,23 @@ Map _$$SpotubeTrackObjectImplToJson( _$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) => _$SpotubeUserObjectImpl( - uid: json['uid'] as String, + id: json['id'] as String, name: json['name'] as String, - avatars: (json['avatars'] as List?) + images: (json['images'] as List?) ?.map((e) => SpotubeImageObject.fromJson( Map.from(e as Map))) .toList() ?? const [], - externalUrl: json['externalUrl'] as String, - displayName: json['displayName'] as String, + externalUri: json['externalUri'] as String, ); Map _$$SpotubeUserObjectImplToJson( _$SpotubeUserObjectImpl instance) => { - 'uid': instance.uid, + 'id': instance.id, 'name': instance.name, - 'avatars': instance.avatars.map((e) => e.toJson()).toList(), - 'externalUrl': instance.externalUrl, - 'displayName': instance.displayName, + 'images': instance.images.map((e) => e.toJson()).toList(), + 'externalUri': instance.externalUri, }; _$PluginConfigurationImpl _$$PluginConfigurationImplFromJson(Map json) => diff --git a/lib/models/metadata/user.dart b/lib/models/metadata/user.dart index 878f2da6..cd041f9c 100644 --- a/lib/models/metadata/user.dart +++ b/lib/models/metadata/user.dart @@ -3,11 +3,10 @@ part of 'metadata.dart'; @freezed class SpotubeUserObject with _$SpotubeUserObject { factory SpotubeUserObject({ - required final String uid, + required final String id, required final String name, - @Default([]) final List avatars, - required final String externalUrl, - required final String displayName, + @Default([]) final List images, + required final String externalUri, }) = _SpotubeUserObject; factory SpotubeUserObject.fromJson(Map json) => diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index 35ea5fdd..283cf715 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -7,6 +7,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:file_picker/file_picker.dart'; @@ -21,10 +22,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final plugins = ref.watch(metadataPluginsProvider); final pluginsNotifier = ref.watch(metadataPluginsProvider.notifier); - final metadataApi = ref.watch(metadataPluginApiProvider); - final isAuthenticated = ref.watch(metadataAuthenticatedProvider); - - final artists = ref.watch(metadataUserArtistsProvider); + final metadataPlugin = ref.watch(metadataPluginProvider); + final isAuthenticated = ref.watch(metadataPluginAuthenticatedProvider); return Scaffold( headers: const [ @@ -111,9 +110,7 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final plugin = plugins.asData!.value.plugins[index]; final isDefault = plugins.asData!.value.defaultPlugin == index; final requiresAuth = isDefault && - metadataApi.hasValue && - metadataApi.asData?.value?.signatureFlags.requiresAuth == - true; + plugin.abilities.contains(PluginAbilities.authentication); return Card( child: Column( spacing: 8, @@ -153,8 +150,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { if (isAuthenticated.asData?.value != true) Button.primary( onPressed: () async { - await metadataApi.asData?.value - ?.authenticate(); + await metadataPlugin.asData?.value?.auth + .authenticate(); }, leading: const Icon(SpotubeIcons.login), child: const Text("Login"), @@ -162,7 +159,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { else Button.destructive( onPressed: () async { - await metadataApi.asData?.value?.logout(); + await metadataPlugin.asData?.value?.auth + .logout(); }, leading: const Icon(SpotubeIcons.logout), child: const Text("Logout"), diff --git a/lib/pages/webview/webview.dart b/lib/pages/webview/webview.dart deleted file mode 100644 index b4ce47b3..00000000 --- a/lib/pages/webview/webview.dart +++ /dev/null @@ -1,56 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/components/button/back_button.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/services/metadata/apis/webview.dart'; - -@RoutePage() -class WebviewPage extends StatelessWidget { - final WebviewInitialSettings? initialSettings; - final String? url; - final void Function(InAppWebViewController controller, WebUri? url)? - onLoadStop; - - const WebviewPage({ - super.key, - this.initialSettings, - this.url, - this.onLoadStop, - }); - - @override - Widget build(BuildContext context) { - return SafeArea( - bottom: false, - child: Scaffold( - headers: const [ - TitleBar( - leading: [BackButton(color: Colors.white)], - backgroundColor: Colors.transparent, - ), - ], - floatingHeader: true, - child: InAppWebView( - initialSettings: InAppWebViewSettings( - userAgent: initialSettings?.userAgent ?? - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36", - incognito: initialSettings?.incognito ?? false, - clearCache: initialSettings?.clearCache ?? false, - clearSessionCache: initialSettings?.clearSessionCache ?? false, - ), - initialUrlRequest: URLRequest( - url: WebUri("https://accounts.spotify.com/"), - ), - onPermissionRequest: (controller, permissionRequest) async { - return PermissionResponse( - resources: permissionRequest.resources, - action: PermissionResponseAction.GRANT, - ); - }, - onLoadStop: onLoadStop, - ), - ), - ); - } -} diff --git a/lib/provider/metadata_plugin/auth.dart b/lib/provider/metadata_plugin/auth.dart index 0ccc9cd4..9aa696fc 100644 --- a/lib/provider/metadata_plugin/auth.dart +++ b/lib/provider/metadata_plugin/auth.dart @@ -1,49 +1,37 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'dart:async'; + +import 'package:riverpod/riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -class MetadataAuthenticationNotifier extends AsyncNotifier { - MetadataAuthenticationNotifier(); +class MetadataPluginAuthenticatedNotifier extends AsyncNotifier { @override - build() async { - final metadataApi = await ref.watch(metadataPluginApiProvider.future); - - if (metadataApi?.signatureFlags.requiresAuth != true) { + FutureOr build() async { + final defaultPluginConfig = ref.watch(metadataPluginsProvider); + if (defaultPluginConfig.asData?.value.defaultPluginConfig?.abilities + .contains(PluginAbilities.authentication) != + true) { return false; } - final subscription = metadataApi?.authenticatedStream.listen((event) { - state = AsyncValue.data(event); + final defaultPlugin = await ref.watch(metadataPluginProvider.future); + if (defaultPlugin == null) { + return false; + } + + final sub = defaultPlugin.auth.authStateStream.listen((event) { + state = AsyncData(defaultPlugin.auth.isAuthenticated()); }); ref.onDispose(() { - subscription?.cancel(); + sub.cancel(); }); - return await metadataApi?.isAuthenticated() ?? false; - } - - Future login() async { - final metadataApi = await ref.read(metadataPluginApiProvider.future); - - if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) { - return; - } - - await metadataApi.authenticate(); - } - - Future logout() async { - final metadataApi = await ref.read(metadataPluginApiProvider.future); - - if (metadataApi == null || !metadataApi.signatureFlags.requiresAuth) { - return; - } - - await metadataApi.logout(); + return defaultPlugin.auth.isAuthenticated(); } } -final metadataAuthenticatedProvider = - AsyncNotifierProvider( - () => MetadataAuthenticationNotifier(), +final metadataPluginAuthenticatedProvider = + AsyncNotifierProvider( + MetadataPluginAuthenticatedNotifier.new, ); diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart index 2852c537..81199699 100644 --- a/lib/provider/metadata_plugin/metadata_plugin_provider.dart +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -10,7 +10,6 @@ import 'package:path_provider/path_provider.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/services/dio/dio.dart'; import 'package:spotube/services/metadata/metadata.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -315,20 +314,20 @@ class MetadataPluginNotifier extends AsyncNotifier { ); } - Future getPluginLibraryCode(PluginConfiguration plugin) async { + Future getPluginByteCode(PluginConfiguration plugin) async { final pluginDir = await _getPluginDir(); final pluginExtractionDirPath = join( pluginDir.path, ServiceUtils.sanitizeFilename(plugin.name), ); - final libraryFile = File(join(pluginExtractionDirPath, "dist", "index.js")); + final libraryFile = File(join(pluginExtractionDirPath, "plugin.out")); if (!libraryFile.existsSync()) { - throw Exception("No dist/index.js found"); + throw Exception("No plugin.out (Bytecode) file found"); } - return await libraryFile.readAsString(); + return await libraryFile.readAsBytes(); } } @@ -337,7 +336,7 @@ final metadataPluginsProvider = MetadataPluginNotifier.new, ); -final metadataPluginApiProvider = FutureProvider( +final metadataPluginProvider = FutureProvider( (ref) async { final defaultPlugin = await ref.watch( metadataPluginsProvider.selectAsync((data) => data.defaultPluginConfig), @@ -348,38 +347,9 @@ final metadataPluginApiProvider = FutureProvider( } final pluginsNotifier = ref.read(metadataPluginsProvider.notifier); - final libraryCode = - await pluginsNotifier.getPluginLibraryCode(defaultPlugin); + final pluginByteCode = + await pluginsNotifier.getPluginByteCode(defaultPlugin); - return MetadataApiSignature.init(libraryCode, defaultPlugin); + return await MetadataPlugin.create(defaultPlugin, pluginByteCode); }, ); - -final metadataProviderUserProvider = FutureProvider( - (ref) async { - final metadataApi = await ref.watch(metadataPluginApiProvider.future); - ref.watch(metadataAuthenticatedProvider); - - if (metadataApi == null) { - return null; - } - return metadataApi.getMe(); - }, -); - -final metadataUserArtistsProvider = - FutureProvider>((ref) async { - final metadataApi = await ref.watch(metadataPluginApiProvider.future); - ref.watch(metadataAuthenticatedProvider); - - final userId = await ref.watch( - metadataProviderUserProvider.selectAsync((data) => data?.uid), - ); - if (metadataApi == null || userId == null) { - return []; - } - - final res = await metadataApi.listUserSavedArtists(userId); - - return res.items as List; -}); diff --git a/lib/provider/metadata_plugin/user.dart b/lib/provider/metadata_plugin/user.dart new file mode 100644 index 00000000..7dd65766 --- /dev/null +++ b/lib/provider/metadata_plugin/user.dart @@ -0,0 +1,16 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; + +final metadataPluginUserProvider = FutureProvider( + (ref) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + ref.watch(metadataPluginAuthenticatedProvider); + + if (metadataPlugin == null) { + return null; + } + return metadataPlugin.user.me(); + }, +); diff --git a/lib/services/metadata/apis/localstorage.dart b/lib/services/metadata/apis/localstorage.dart index 3b76f169..4c511e77 100644 --- a/lib/services/metadata/apis/localstorage.dart +++ b/lib/services/metadata/apis/localstorage.dart @@ -1,60 +1,78 @@ -import 'package:flutter_js/flutter_js.dart'; +import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart'; import 'package:shared_preferences/shared_preferences.dart'; -class PluginLocalStorageApi { - final JavascriptRuntime runtime; - final SharedPreferences sharedPreferences; +class SharedPreferencesLocalStorage implements Localstorage { + final SharedPreferences _prefs; + final String pluginSlug; - final String pluginName; + SharedPreferencesLocalStorage(this._prefs, this.pluginSlug); - PluginLocalStorageApi({ - required this.runtime, - required this.sharedPreferences, - required this.pluginName, - }) { - runtime.onMessage("LocalStorage.getItem", (args) { - final key = args[0]["key"]; - final value = getItem(key); - runtime.evaluate( - """ - eventEmitter.emit('LocalStorage.getItem', ${value != null ? "'$value'" : "null"}); - """, - ); - }); - - runtime.onMessage("LocalStorage.setItem", (args) { - final map = args[0] as Map; - setItem(map["key"], map["value"]); - }); - - runtime.onMessage("LocalStorage.removeItem", (args) { - final map = args[0]; - removeItem(map["key"]); - }); - - runtime.onMessage("LocalStorage.clear", (args) { - clear(); - }); + String prefix(String key) { + return 'spotube_plugin.$pluginSlug.$key'; } - void setItem(String key, String value) async { - await sharedPreferences.setString("plugin.$pluginName.$key", value); + @override + Future clear() { + return _prefs.clear(); } - String? getItem(String key) { - return sharedPreferences.getString("plugin.$pluginName.$key"); + @override + Future containsKey(String key) async { + return _prefs.containsKey(prefix(key)); } - void removeItem(String key) async { - await sharedPreferences.remove("plugin.$pluginName.$key"); + @override + Future getBool(String key) async { + return _prefs.getBool(prefix(key)); } - void clear() async { - final keys = sharedPreferences.getKeys(); - for (String key in keys) { - if (key.startsWith("plugin.$pluginName.")) { - await sharedPreferences.remove(key); - } - } + @override + Future getDouble(String key) async { + return _prefs.getDouble(prefix(key)); + } + + @override + Future getInt(String key) async { + return _prefs.getInt(prefix(key)); + } + + @override + Future getString(String key) async { + return _prefs.getString(prefix(key)); + } + + @override + Future?> getStringList(String key) async { + return _prefs.getStringList(prefix(key)); + } + + @override + Future remove(String key) async { + await _prefs.remove(prefix(key)); + } + + @override + Future setBool(String key, bool value) async { + await _prefs.setBool(prefix(key), value); + } + + @override + Future setDouble(String key, double value) async { + await _prefs.setDouble(prefix(key), value); + } + + @override + Future setInt(String key, int value) async { + await _prefs.setInt(prefix(key), value); + } + + @override + Future setString(String key, String value) async { + await _prefs.setString(prefix(key), value); + } + + @override + Future setStringList(String key, List value) async { + await _prefs.setStringList(prefix(key), value); } } diff --git a/lib/services/metadata/apis/set_interval.dart b/lib/services/metadata/apis/set_interval.dart deleted file mode 100644 index f059dc75..00000000 --- a/lib/services/metadata/apis/set_interval.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; - -import 'package:flutter_js/flutter_js.dart'; - -class PluginSetIntervalApi { - final JavascriptRuntime runtime; - - final Map _timers = {}; - - PluginSetIntervalApi(this.runtime) { - runtime.evaluate( - """ - var __NATIVE_FLUTTER_JS__setIntervalCount = -1; - var __NATIVE_FLUTTER_JS__setIntervalCallbacks = {}; - function setInterval(fnInterval, interval) { - try { - __NATIVE_FLUTTER_JS__setIntervalCount += 1; - var intervalIndex = '' + __NATIVE_FLUTTER_JS__setIntervalCount; - __NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex] = fnInterval; - ; - sendMessage('PluginSetIntervalApi.setInterval', JSON.stringify({ intervalIndex, interval})); - return intervalIndex; - } catch (e) { - console.error('ERROR HERE',e.message); - } - }; - - function clearInterval(intervalIndex) { - try { - delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[intervalIndex]; - sendMessage('PluginSetIntervalApi.clearInterval', JSON.stringify({ intervalIndex})); - } catch (e) { - console.error('ERROR HERE',e.message); - } - }; - 1 - """, - ); - - runtime.onMessage('PluginSetIntervalApi.setInterval', (dynamic args) { - try { - int duration = args['interval'] ?? 0; - String idx = args['intervalIndex']; - - _timers[idx] = - Timer.periodic(Duration(milliseconds: duration), (timer) { - runtime.evaluate(""" - __NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx].call(); - delete __NATIVE_FLUTTER_JS__setIntervalCallbacks[$idx]; - """); - }); - } on Exception catch (e) { - print('Exception no setInterval: $e'); - } on Error catch (e) { - print('Erro no setInterval: $e'); - } - }); - - runtime.onMessage('PluginSetIntervalApi.clearInterval', (dynamic args) { - try { - String idx = args['intervalIndex']; - if (_timers.containsKey(idx)) { - _timers[idx]?.cancel(); - _timers.remove(idx); - } - } on Exception catch (e) { - print('Exception no clearInterval: $e'); - } on Error catch (e) { - print('Error no clearInterval: $e'); - } - }); - } - - void dispose() { - for (var timer in _timers.values) { - timer.cancel(); - } - _timers.clear(); - } -} diff --git a/lib/services/metadata/apis/totp.dart b/lib/services/metadata/apis/totp.dart deleted file mode 100644 index 7a0bdb90..00000000 --- a/lib/services/metadata/apis/totp.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:flutter_js/javascript_runtime.dart'; -import 'package:otp_util/otp_util.dart'; -// ignore: implementation_imports -import 'package:otp_util/src/utils/generic_util.dart'; - -class PluginTotpGenerator { - final JavascriptRuntime runtime; - - PluginTotpGenerator(this.runtime) { - runtime.onMessage("TotpGenerator.generate", (args) { - final opts = args[0]; - if (opts is! Map) { - return; - } - - final totp = TOTP( - secret: opts["secret"] as String, - algorithm: OTPAlgorithm.values.firstWhere( - (e) => e.name == opts["algorithm"], - orElse: () => OTPAlgorithm.SHA1, - ), - digits: opts["digits"] as int? ?? 6, - interval: opts["interval"] as int? ?? 30, - ); - - final otp = totp.generateOTP( - input: Util.timeFormat( - time: DateTime.fromMillisecondsSinceEpoch(opts["period"]), - interval: 30, - ), - ); - - runtime.evaluate( - """ - eventEmitter.emit('TotpGenerator.generate', '$otp'); - """, - ); - }); - } -} diff --git a/lib/services/metadata/apis/webview.dart b/lib/services/metadata/apis/webview.dart deleted file mode 100644 index 5ad74060..00000000 --- a/lib/services/metadata/apis/webview.dart +++ /dev/null @@ -1,171 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:auto_route/auto_route.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; -import 'package:flutter_js/flutter_js.dart'; -import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; -import 'package:spotube/collections/routes.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/pages/mobile_login/no_webview_runtime_dialog.dart'; -import 'package:spotube/utils/platform.dart'; - -class WebviewInitialSettings { - final String? userAgent; - final bool? incognito; - final bool? clearCache; - final bool? clearSessionCache; - - WebviewInitialSettings({ - this.userAgent, - this.incognito, - this.clearCache, - this.clearSessionCache, - }); - - factory WebviewInitialSettings.fromJson(Map json) { - return WebviewInitialSettings( - userAgent: json["userAgent"], - incognito: json["incognito"], - clearCache: json["clearCache"], - clearSessionCache: json["clearSessionCache"], - ); - } -} - -class PluginWebViewApi { - JavascriptRuntime runtime; - - PluginWebViewApi({ - required this.runtime, - }) { - runtime.onMessage("WebView.show", (args) { - if (args[0] is! Map) { - return; - } - showWebView( - url: args[0]["url"] as String, - initialSettings: args[0]["initialSettings"] != null - ? WebviewInitialSettings.fromJson( - args[0]["initialSettings"], - ) - : null, - ); - }); - } - - Webview? webviewWindow; - - Future showWebView({ - required String url, - WebviewInitialSettings? initialSettings, - }) async { - if (rootNavigatorKey.currentContext == null) { - return; - } - final context = rootNavigatorKey.currentContext!; - final theme = Theme.of(context); - - if (kIsMobile || kIsMacOS) { - context.pushRoute(WebviewRoute( - initialSettings: initialSettings, - url: url, - onLoadStop: (controller, uri) async { - if (uri == null) return; - final cookies = await CookieManager().getAllCookies(); - - final jsonCookies = cookies.map((e) { - return { - "name": e.name, - "value": e.value, - "domain": e.domain, - "path": e.path, - }; - }); - - runtime.onMessage("WebView.close", (args) { - context.back(); - }); - - runtime.evaluate( - """ - eventEmitter.emit('WebView.onLoadFinish', {url: '${uri.toString()}', cookies: ${jsonEncode(jsonCookies)}}); - """, - ); - }, - )); - return; - } - - try { - final applicationSupportDir = await getApplicationSupportDirectory(); - final userDataFolder = Directory( - join(applicationSupportDir.path, "webview_window_Webview2"), - ); - - if (!await userDataFolder.exists()) { - await userDataFolder.create(); - } - - final webview = await WebviewWindow.create( - configuration: CreateConfiguration( - title: "Webview", - titleBarTopPadding: kIsMacOS ? 20 : 0, - windowHeight: 720, - windowWidth: 1280, - userDataFolderWindows: userDataFolder.path, - ), - ); - - webviewWindow = webview; - - runtime.onMessage("WebView.close", (args) { - webview.close(); - }); - - webview - ..setBrightness(theme.colorScheme.brightness) - ..launch(url) - ..setOnUrlRequestCallback((url) { - () async { - final cookies = await webview.getAllCookies(); - final jsonCookies = cookies.map((e) { - return { - "name": e.name, - "value": e.value, - "domain": e.domain, - "path": e.path, - }; - }).toList(); - - runtime.evaluate( - """ - eventEmitter.emit('WebView.onLoadFinish', {url: '$url', cookies: ${jsonEncode(jsonCookies)}}); - """, - ); - }(); - return false; - }); - } on PlatformException catch (_) { - if (!await WebviewWindow.isWebviewAvailable()) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - showDialog( - context: context, - builder: (context) { - return const NoWebviewRuntimeDialog(); - }, - ); - }); - } - } - } - - void dispose() { - webviewWindow?.close(); - webviewWindow = null; - } -} diff --git a/lib/services/metadata/endpoints/album.dart b/lib/services/metadata/endpoints/album.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/artist.dart b/lib/services/metadata/endpoints/artist.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/auth.dart b/lib/services/metadata/endpoints/auth.dart new file mode 100644 index 00000000..7c2077be --- /dev/null +++ b/lib/services/metadata/endpoints/auth.dart @@ -0,0 +1,33 @@ +import 'package:desktop_webview_window/desktop_webview_window.dart'; +import 'package:flutter_inappwebview/flutter_inappwebview.dart'; +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_std/hetu_std.dart'; +import 'package:spotube/utils/platform.dart'; + +class MetadataAuthEndpoint { + final Hetu hetu; + + MetadataAuthEndpoint(this.hetu); + + Stream get authStateStream => + hetu.eval("metadataPlugin.auth.authStateStream"); + + Future authenticate() async { + await hetu.eval("metadataPlugin.auth.authenticate()"); + } + + bool isAuthenticated() { + return hetu.eval("metadataPlugin.auth.isAuthenticated()") as bool; + } + + Future logout() async { + await hetu.eval("metadataPlugin.auth.logout()"); + if (kIsMobile) { + WebStorageManager.instance().deleteAllData(); + CookieManager.instance().deleteAllCookies(); + } + if (kIsDesktop) { + await WebviewWindow.clearAll(); + } + } +} diff --git a/lib/services/metadata/endpoints/browse.dart b/lib/services/metadata/endpoints/browse.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/playlist.dart b/lib/services/metadata/endpoints/playlist.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/search.dart b/lib/services/metadata/endpoints/search.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/track.dart b/lib/services/metadata/endpoints/track.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/services/metadata/endpoints/user.dart b/lib/services/metadata/endpoints/user.dart new file mode 100644 index 00000000..ec4cd963 --- /dev/null +++ b/lib/services/metadata/endpoints/user.dart @@ -0,0 +1,15 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginUserEndpoint { + final Hetu hetu; + MetadataPluginUserEndpoint(this.hetu); + + Future me() async { + final raw = await hetu.eval("metadataPlugin.user.me()") as Map; + + return SpotubeUserObject.fromJson( + raw.cast(), + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index b01e41be..8f219207 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -1,564 +1,81 @@ -import 'dart:async'; -import 'dart:convert'; +import 'dart:typed_data'; -import 'package:flutter_js/extensions/fetch.dart'; -import 'package:flutter_js/extensions/xhr.dart'; -import 'package:flutter_js/flutter_js.dart'; +import 'package:auto_route/auto_route.dart'; +import 'package:hetu_otp_util/hetu_otp_util.dart'; +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_spotube_plugin/hetu_spotube_plugin.dart'; +import 'package:hetu_std/hetu_std.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'package:spotube/collections/routes.dart'; +import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; -import 'package:spotube/services/metadata/apis/set_interval.dart'; -import 'package:spotube/services/metadata/apis/totp.dart'; -import 'package:spotube/services/metadata/apis/webview.dart'; +import 'package:spotube/services/metadata/endpoints/auth.dart'; +import 'package:spotube/services/metadata/endpoints/user.dart'; const defaultMetadataLimit = "20"; -class MetadataSignatureFlags { - final bool requiresAuth; - - const MetadataSignatureFlags({ - this.requiresAuth = false, - }); - - factory MetadataSignatureFlags.fromJson(Map json) { - return MetadataSignatureFlags( - requiresAuth: json["requiresAuth"] ?? false, - ); - } -} - -/// Signature for metadata and related methods that will return Spotube native -/// objects e.g. SpotubeTrack, SpotubePlaylist, etc. -class MetadataApiSignature { - final JavascriptRuntime runtime; - final PluginLocalStorageApi localStorageApi; - final PluginWebViewApi webViewApi; - final PluginTotpGenerator totpGenerator; - final PluginSetIntervalApi setIntervalApi; - late MetadataSignatureFlags _signatureFlags; - - final StreamController _authenticatedStreamController; - - Stream get authenticatedStream => _authenticatedStreamController.stream; - - MetadataSignatureFlags get signatureFlags => _signatureFlags; - - MetadataApiSignature._( - this.runtime, - this.localStorageApi, - this.webViewApi, - this.totpGenerator, - this.setIntervalApi, - ) : _authenticatedStreamController = StreamController.broadcast() { - runtime.onMessage("authenticatedStatus", (args) { - if (args[0] is Map && (args[0] as Map).containsKey("authenticated")) { - final authenticated = args[0]["authenticated"] as bool; - _authenticatedStreamController.add(authenticated); - } - }); - } - - static Future init( - String libraryCode, +class MetadataPlugin { + static Future create( PluginConfiguration config, + Uint8List byteCode, ) async { - final runtime = getJavascriptRuntime(xhr: true).enableXhr(); - runtime.enableHandlePromises(); - await runtime.enableFetch(); + final sharedPreferences = await SharedPreferences.getInstance(); + BuildContext? pageContext; - Timer.periodic( - const Duration(milliseconds: 100), - (timer) { - runtime.executePendingJob(); + final hetu = Hetu(); + hetu.init(); + + HetuStdLoader.loadBindings(hetu); + HetuSpotubePluginLoader.loadBindings( + hetu, + localStorageImpl: SharedPreferencesLocalStorage( + sharedPreferences, + config.slug, + ), + onNavigatorPush: (route) { + return rootNavigatorKey.currentContext?.router + .pushWidget(Builder(builder: (context) { + pageContext = context; + return Scaffold( + headers: const [ + TitleBar( + automaticallyImplyLeading: true, + ) + ], + child: route, + ); + })); + }, + onNavigatorPop: () { + pageContext?.maybePop(); }, ); - // Create all the PluginAPIs after library code is evaluated - final localStorageApi = PluginLocalStorageApi( - runtime: runtime, - sharedPreferences: await SharedPreferences.getInstance(), - pluginName: config.slug, - ); + await HetuStdLoader.loadBytecodeFlutter(hetu); + await HetuOtpUtilLoader.loadBytecodeFlutter(hetu); + await HetuSpotubePluginLoader.loadBytecodeFlutter(hetu); - final webViewApi = PluginWebViewApi(runtime: runtime); - final totpGenerator = PluginTotpGenerator(runtime); - final setIntervalApi = PluginSetIntervalApi(runtime); + hetu.loadBytecode(bytes: byteCode, moduleName: "plugin"); + hetu.eval(""" + import "module:plugin" as plugin - final metadataApi = MetadataApiSignature._( - runtime, - localStorageApi, - webViewApi, - totpGenerator, - setIntervalApi, - ); + var Plugin = plugin.${config.entryPoint} - final res = runtime.evaluate( - """ - ;$libraryCode; - const metadataApi = new MetadataApi(); - """, - ); - metadataApi._signatureFlags = await metadataApi._getSignatureFlags(); + var metadataPlugin = Plugin() + """); - if (res.isError) { - AppLogger.reportError( - "Error evaluating code: $libraryCode\n${res.rawResult}", - ); - } - - return metadataApi; + return MetadataPlugin._(hetu); } - void dispose() { - setIntervalApi.dispose(); - webViewApi.dispose(); - runtime.dispose(); - } + final Hetu hetu; - Future invoke(String method, [List? args]) async { - final completer = Completer(); - runtime.onMessage(method, (result) { - if (completer.isCompleted) return; - try { - if (result is Map && result.containsKey("error")) { - completer.completeError(result["error"]); - } else { - completer.complete(result is String ? jsonDecode(result) : result); - } - } catch (e, stack) { - AppLogger.reportError( - "[MetadataApiSignature][invoke] Error in $method: $e", - stack, - ); - } - }); - final code = """ - $method(...${args != null ? jsonEncode(args) : "[]"}) - .then((res) => { - try { - sendMessage("$method", res ? JSON.stringify(res) : "[]"); - } catch (e) { - console.error("Failed to send message in $method.then: ", `\${e.toString()}\n\${e.stack.toString()}`); - } - }).catch((e) => { - try { - console.error("Error in $method: ", `\${e.toString()}\n\${e.stack.toString()}`); - sendMessage("$method", JSON.stringify({error: `\${e.toString()}\n\${e.stack.toString()}`})); - } catch (e) { - console.error("Failed to send message in $method.catch: ", `\${e.toString()}\n\${e.stack.toString()}`); - } - }); - """; + late final MetadataAuthEndpoint auth; + late final MetadataPluginUserEndpoint user; - final res = await runtime.evaluateAsync(code); - - if (res.isError) { - AppLogger.reportError("Error evaluating code: $code\n${res.rawResult}"); - completer.completeError("Error evaluating code: $code\n${res.rawResult}"); - return completer.future; - } - - return completer.future; - } - - Future _getSignatureFlags() async { - final res = await invoke("metadataApi.getSignatureFlags"); - - return MetadataSignatureFlags.fromJson(res); - } - - // ----- Authentication ------ - - Future authenticate() async { - await invoke("metadataApi.authenticate"); - } - - Future isAuthenticated() async { - final res = await invoke("metadataApi.isAuthenticated"); - return res as bool; - } - - Future logout() async { - await invoke("metadataApi.logout"); - } - - // ----- Track ------ - Future getTrack(String id) async { - final result = await invoke("metadataApi.getTrack", [id]); - return SpotubeTrackObject.fromJson(result); - } - - Future> listTracks({ - List? ids, - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final result = await invoke( - "metadataApi.listTracks", - [ - ids, - limit, - cursor, - ], - ); - - return SpotubePaginationResponseObject.fromJson( - result, - SpotubeTrackObject.fromJson, - ); - } - - Future> listTracksByAlbum( - String albumId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listTracksByAlbum", - [albumId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeTrackObject.fromJson, - ); - } - - Future> - listTopTracksByArtist( - String artistId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listTopTracksByArtist", - [artistId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeTrackObject.fromJson, - ); - } - - Future> - listTracksByPlaylist( - String playlistId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listTracksByPlaylist", - [playlistId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeTrackObject.fromJson, - ); - } - - Future> - listUserSavedTracks( - String userId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listUserSavedTracks", - [userId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeTrackObject.fromJson, - ); - } - - // ----- Album ------ - Future getAlbum(String id) async { - final res = await invoke("metadataApi.getAlbum", [id]); - - return SpotubeAlbumObject.fromJson(res); - } - - Future> listAlbums({ - List? ids, - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listAlbums", - [ids, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeAlbumObject.fromJson, - ); - } - - Future> - listAlbumsByArtist( - String artistId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listAlbumsByArtist", - [artistId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeAlbumObject.fromJson, - ); - } - - Future> - listUserSavedAlbums( - String userId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listUserSavedAlbums", - [userId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeAlbumObject.fromJson, - ); - } - - // ----- Playlist ------ - Future getPlaylist(String id) async { - final res = await invoke("metadataApi.getPlaylist", [id]); - - return SpotubePlaylistObject.fromJson(res); - } - - Future> - listFeedPlaylists( - String feedId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listFeedPlaylists", - [feedId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubePlaylistObject.fromJson, - ); - } - - Future> - listUserSavedPlaylists( - String userId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listUserSavedPlaylists", - [userId, limit, cursor], - ); - - 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 ------ - Future getArtist(String id) async { - final res = await invoke("metadataApi.getArtist", [id]); - - return SpotubeArtistObject.fromJson(res); - } - - Future> listArtists({ - List? ids, - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listArtists", - [ids, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeArtistObject.fromJson, - ); - } - - Future> - listUserSavedArtists( - String userId, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.listUserSavedArtists", - [userId, limit, cursor], - ); - - return SpotubePaginationResponseObject.fromJson( - res, - SpotubeArtistObject.fromJson, - ); - } - - // ----- Search ------ - Future search( - String query, { - String limit = defaultMetadataLimit, - String? cursor, - }) async { - final res = await invoke( - "metadataApi.search", - [query, limit, cursor], - ); - - 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 ------ - Future getMe() async { - final res = await invoke("metadataApi.getMe"); - - return SpotubeUserObject.fromJson(res); - } - - Future followArtist(String userId, String artistId) async { - await invoke("metadataApi.followArtist", [userId, artistId]); - } - - Future unfollowArtist(String userId, String artistId) async { - await invoke("metadataApi.unfollowArtist", [userId, artistId]); - } - - Future savePlaylist(String userId, String playlistId) async { - await invoke("metadataApi.savePlaylist", [userId, playlistId]); - } - - Future unsavePlaylist(String userId, String playlistId) async { - await invoke("metadataApi.unsavePlaylist", [userId, playlistId]); - } - - Future saveAlbum(String userId, String albumId) async { - await invoke("metadataApi.saveAlbum", [userId, albumId]); - } - - Future unsaveAlbum(String userId, String albumId) async { - await invoke("metadataApi.unsaveAlbum", [userId, albumId]); - } - - Future saveTrack(String userId, String trackId) async { - await invoke("metadataApi.saveTrack", [userId, trackId]); - } - - Future unsaveTrack(String userId, String trackId) async { - await invoke("metadataApi.unsaveTrack", [userId, trackId]); + MetadataPlugin._(this.hetu) { + auth = MetadataAuthEndpoint(hetu); + user = MetadataPluginUserEndpoint(hetu); } } diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index 3976fd80..8a9a2e19 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #include #include @@ -28,12 +28,12 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); file_selector_plugin_register_with_registrar(file_selector_linux_registrar); - g_autoptr(FlPluginRegistrar) flutter_js_registrar = - fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterJsPlugin"); - flutter_js_plugin_register_with_registrar(flutter_js_registrar); g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) flutter_timezone_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterTimezonePlugin"); + flutter_timezone_plugin_register_with_registrar(flutter_timezone_registrar); g_autoptr(FlPluginRegistrar) gtk_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin"); gtk_plugin_register_with_registrar(gtk_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index c47d2627..783f370f 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,8 +5,8 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_linux - flutter_js flutter_secure_storage_linux + flutter_timezone gtk local_notifier media_kit_libs_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index d44e5a13..bc163169 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,8 +14,8 @@ import desktop_webview_window import device_info_plus import file_selector_macos import flutter_inappwebview_macos -import flutter_js import flutter_secure_storage_macos +import flutter_timezone import local_notifier import media_kit_libs_macos_audio import open_file_mac @@ -40,8 +40,8 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) InAppWebViewFlutterPlugin.register(with: registry.registrar(forPlugin: "InAppWebViewFlutterPlugin")) - FlutterJsPlugin.register(with: registry.registrar(forPlugin: "FlutterJsPlugin")) FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin")) + FlutterTimezonePlugin.register(with: registry.registrar(forPlugin: "FlutterTimezonePlugin")) LocalNotifierPlugin.register(with: registry.registrar(forPlugin: "LocalNotifierPlugin")) MediaKitLibsMacosAudioPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosAudioPlugin")) OpenFilePlugin.register(with: registry.registrar(forPlugin: "OpenFilePlugin")) diff --git a/pubspec.lock b/pubspec.lock index dcb307a9..8c5211f7 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -540,10 +540,10 @@ packages: dependency: "direct main" description: name: dio - sha256: "5598aa796bbf4699afd5c67c0f5f6e2ed542afc956884b9cd58c306966efc260" + sha256: "253a18bbd4851fecba42f7343a1df3a9a4c1d31a2c1b37e221086b4fa8c8dbc9" url: "https://pub.dev" source: hosted - version: "5.7.0" + version: "5.8.0+1" dio_http2_adapter: dependency: "direct main" description: @@ -658,6 +658,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.2" + fast_noise: + dependency: transitive + description: + name: fast_noise + sha256: "271031cebf1602fc064472970e658fa7ff2f3b55a979bb430337b715bd55690b" + url: "https://pub.dev" + source: hosted + version: "1.0.1" ffi: dependency: transitive description: @@ -909,14 +917,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.0" - flutter_js: - dependency: "direct main" - description: - name: flutter_js - sha256: "6b777cd4e468546f046a2f114d078a4596143269f6fa6bad5c29611d5b896369" - url: "https://pub.dev" - source: hosted - version: "0.8.2" flutter_launcher_icons: dependency: "direct dev" description: @@ -1048,6 +1048,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_timezone: + dependency: transitive + description: + name: flutter_timezone + sha256: "13b2109ad75651faced4831bf262e32559e44aa549426eab8a597610d385d934" + url: "https://pub.dev" + source: hosted + version: "4.1.1" flutter_undraw: dependency: "direct main" description: @@ -1122,6 +1130,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.1" + get_it: + dependency: "direct main" + description: + name: get_it + sha256: f126a3e286b7f5b578bf436d5592968706c4c1de28a228b870ce375d9f743103 + url: "https://pub.dev" + source: hosted + version: "8.0.3" glob: dependency: transitive description: @@ -1170,6 +1186,41 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + hetu_otp_util: + dependency: "direct main" + description: + path: "." + ref: main + resolved-ref: "7790606751828758769596e809ba6cbb756f9b9a" + url: "https://github.com/hetu-community/hetu_otp_util.git" + source: git + version: "1.0.0" + hetu_script: + dependency: "direct main" + description: + name: hetu_script + sha256: a9a3f4f510ae10188d352b79e8fc45e27134cfa81ee1691004067395f45f17cb + url: "https://pub.dev" + source: hosted + version: "0.4.2+1" + hetu_spotube_plugin: + dependency: "direct main" + description: + path: "." + ref: main + resolved-ref: "1aa924281d2dbe09aab27d8c2de1cffc853b0d16" + url: "https://github.com/KRTirtho/hetu_spotube_plugin.git" + source: git + version: "0.0.1" + hetu_std: + dependency: "direct main" + description: + path: "." + ref: main + resolved-ref: d3720be2a92022f7b95a3082d40322d8458c70da + url: "https://github.com/hetu-community/hetu_std.git" + source: git + version: "1.0.0" home_widget: dependency: "direct main" description: @@ -2783,5 +2834,5 @@ packages: source: git version: "1.0.0" sdks: - dart: ">=3.7.0-0 <4.0.0" + dart: ">=3.7.2 <4.0.0" flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index 6c7c1d8b..4dd74570 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -142,8 +142,21 @@ dependencies: collection: any otp_util: ^1.0.2 dio_http2_adapter: ^2.6.0 - flutter_js: ^0.8.2 archive: ^4.0.7 + hetu_script: ^0.4.2+1 + hetu_std: + git: + url: https://github.com/hetu-community/hetu_std.git + ref: main + hetu_otp_util: + git: + url: https://github.com/hetu-community/hetu_otp_util.git + ref: main + hetu_spotube_plugin: + git: + url: https://github.com/KRTirtho/hetu_spotube_plugin.git + ref: main + get_it: ^8.0.3 dev_dependencies: build_runner: ^2.4.13 @@ -170,9 +183,10 @@ dependency_overrides: git: url: https://github.com/KRTirtho/Bonsoir.git path: packages/bonsoir_android - web: ^1.1.0 meta: 1.16.0 + web: ^1.1.0 flutter_svg: ^2.0.17 + intl: any collection: any flutter: @@ -195,6 +209,10 @@ flutter: - packages/flutter_undraw/assets/undraw/taken.svg - packages/flutter_undraw/assets/undraw/empty.svg - packages/flutter_undraw/assets/undraw/no_data.svg + # hetu script bytecode + - packages/hetu_std/assets/bytecode/std.out + - packages/hetu_otp_util/assets/bytecode/otp_util.out + - packages/hetu_spotube_plugin/assets/bytecode/spotube_plugin.out fonts: - family: RadixIcons fonts: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 9ad3a3a5..f053614f 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -12,8 +12,8 @@ #include #include #include -#include #include +#include #include #include #include @@ -37,10 +37,10 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterInappwebviewWindowsPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterInappwebviewWindowsPluginCApi")); - FlutterJsPluginRegisterWithRegistrar( - registry->GetRegistrarForPlugin("FlutterJsPlugin")); FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + FlutterTimezonePluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FlutterTimezonePluginCApi")); LocalNotifierPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("LocalNotifierPlugin")); MediaKitLibsWindowsAudioPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index a9b74bf4..fae5ffe9 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -9,8 +9,8 @@ list(APPEND FLUTTER_PLUGIN_LIST desktop_webview_window file_selector_windows flutter_inappwebview_windows - flutter_js flutter_secure_storage_windows + flutter_timezone local_notifier media_kit_libs_windows_audio permission_handler_windows From 3306f21860c394b4307e1523befc94ede0271be6 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 19:18:32 +0600 Subject: [PATCH 11/60] feat: update models to match hetu_spotube_plugin signature --- lib/models/metadata/album.dart | 44 +- lib/models/metadata/artist.dart | 32 +- lib/models/metadata/browse.dart | 43 + lib/models/metadata/feed.dart | 14 - lib/models/metadata/metadata.dart | 2 +- lib/models/metadata/metadata.freezed.dart | 3495 ++++++++++++++++----- lib/models/metadata/metadata.g.dart | 330 +- lib/models/metadata/pagination.dart | 4 +- lib/models/metadata/playlist.dart | 41 +- lib/models/metadata/search.dart | 67 +- lib/models/metadata/track.dart | 41 +- 11 files changed, 3132 insertions(+), 981 deletions(-) create mode 100644 lib/models/metadata/browse.dart delete mode 100644 lib/models/metadata/feed.dart diff --git a/lib/models/metadata/album.dart b/lib/models/metadata/album.dart index fba880f6..da2c21fd 100644 --- a/lib/models/metadata/album.dart +++ b/lib/models/metadata/album.dart @@ -3,20 +3,40 @@ part of 'metadata.dart'; enum SpotubeAlbumType { album, single, + compilation, } @freezed -class SpotubeAlbumObject with _$SpotubeAlbumObject { - factory SpotubeAlbumObject({ - required final String uid, - required final String title, - required final SpotubeArtistObject artist, - @Default([]) final List images, - required final String releaseDate, - required final String externalUrl, - required final SpotubeAlbumType type, - }) = _SpotubeAlbumObject; +class SpotubeFullAlbumObject with _$SpotubeFullAlbumObject { + factory SpotubeFullAlbumObject({ + required String id, + required String name, + required List artists, + @Default([]) List images, + required String releaseDate, + required String externalUri, + required int totalTracks, + required SpotubeAlbumType albumType, + String? recordLabel, + List? genres, + }) = _SpotubeFullAlbumObject; - factory SpotubeAlbumObject.fromJson(Map json) => - _$SpotubeAlbumObjectFromJson(json); + factory SpotubeFullAlbumObject.fromJson(Map json) => + _$SpotubeFullAlbumObjectFromJson(json); +} + +@freezed +class SpotubeSimpleAlbumObject with _$SpotubeSimpleAlbumObject { + factory SpotubeSimpleAlbumObject({ + required String id, + required String name, + required String externalUri, + required List artists, + @Default([]) List images, + required String releaseDate, + required SpotubeAlbumType albumType, + }) = _SpotubeSimpleAlbumObject; + + factory SpotubeSimpleAlbumObject.fromJson(Map json) => + _$SpotubeSimpleAlbumObjectFromJson(json); } diff --git a/lib/models/metadata/artist.dart b/lib/models/metadata/artist.dart index 2dbb7379..2bee1191 100644 --- a/lib/models/metadata/artist.dart +++ b/lib/models/metadata/artist.dart @@ -1,14 +1,28 @@ part of 'metadata.dart'; @freezed -class SpotubeArtistObject with _$SpotubeArtistObject { - factory SpotubeArtistObject({ - required final String uid, - required final String name, - @Default([]) final List images, - required final String externalUrl, - }) = _SpotubeArtistObject; +class SpotubeFullArtistObject with _$SpotubeFullArtistObject { + factory SpotubeFullArtistObject({ + required String id, + required String name, + required String externalUri, + @Default([]) List images, + List? genres, + int? followers, + }) = _SpotubeFullArtistObject; - factory SpotubeArtistObject.fromJson(Map json) => - _$SpotubeArtistObjectFromJson(json); + factory SpotubeFullArtistObject.fromJson(Map json) => + _$SpotubeFullArtistObjectFromJson(json); +} + +@freezed +class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject { + factory SpotubeSimpleArtistObject({ + required String id, + required String name, + required String externalUri, + }) = _SpotubeSimpleArtistObject; + + factory SpotubeSimpleArtistObject.fromJson(Map json) => + _$SpotubeSimpleArtistObjectFromJson(json); } diff --git a/lib/models/metadata/browse.dart b/lib/models/metadata/browse.dart new file mode 100644 index 00000000..fc323d01 --- /dev/null +++ b/lib/models/metadata/browse.dart @@ -0,0 +1,43 @@ +part of 'metadata.dart'; + +enum SectionItemType { + @JsonValue("Playlist") + playlist, + @JsonValue("Album") + album, + @JsonValue("Artist") + artist +} + +@Freezed(unionKey: "itemType") +class SpotubeBrowseSectionObject with _$SpotubeBrowseSectionObject { + @FreezedUnionValue("Album") + factory SpotubeBrowseSectionObject.album({ + required String id, + required String title, + required String externalUri, + required SectionItemType itemType, + required List items, + }) = SpotubeBrowseAlbumSectionObject; + + @FreezedUnionValue("Artist") + factory SpotubeBrowseSectionObject.artist({ + required String id, + required String title, + required String externalUri, + required SectionItemType itemType, + required List items, + }) = SpotubeBrowseArtistSectionObject; + + @FreezedUnionValue("Playlist") + factory SpotubeBrowseSectionObject.playlist({ + required String id, + required String title, + required String externalUri, + required SectionItemType itemType, + required List items, + }) = SpotubeBrowsePlaylistSectionObject; + + factory SpotubeBrowseSectionObject.fromJson(Map json) => + _$SpotubeBrowseSectionObjectFromJson(json); +} diff --git a/lib/models/metadata/feed.dart b/lib/models/metadata/feed.dart deleted file mode 100644 index 3bf7c99b..00000000 --- a/lib/models/metadata/feed.dart +++ /dev/null @@ -1,14 +0,0 @@ -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 99f7c687..6e70ef99 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -7,7 +7,7 @@ part 'metadata.freezed.dart'; part 'album.dart'; part 'artist.dart'; -part 'feed.dart'; +part 'browse.dart'; part 'image.dart'; part 'pagination.dart'; part 'playlist.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 7f420732..7774224c 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -14,84 +14,94 @@ T _$identity(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'); -SpotubeAlbumObject _$SpotubeAlbumObjectFromJson(Map json) { - return _SpotubeAlbumObject.fromJson(json); +SpotubeFullAlbumObject _$SpotubeFullAlbumObjectFromJson( + Map json) { + return _SpotubeFullAlbumObject.fromJson(json); } /// @nodoc -mixin _$SpotubeAlbumObject { - String get uid => throw _privateConstructorUsedError; - String get title => throw _privateConstructorUsedError; - SpotubeArtistObject get artist => throw _privateConstructorUsedError; +mixin _$SpotubeFullAlbumObject { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + List get artists => + throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; String get releaseDate => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; - SpotubeAlbumType get type => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + int get totalTracks => throw _privateConstructorUsedError; + SpotubeAlbumType get albumType => throw _privateConstructorUsedError; + String? get recordLabel => throw _privateConstructorUsedError; + List? get genres => throw _privateConstructorUsedError; - /// Serializes this SpotubeAlbumObject to a JSON map. + /// Serializes this SpotubeFullAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of SpotubeAlbumObject + /// Create a copy of SpotubeFullAlbumObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubeAlbumObjectCopyWith get copyWith => + $SpotubeFullAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubeAlbumObjectCopyWith<$Res> { - factory $SpotubeAlbumObjectCopyWith( - SpotubeAlbumObject value, $Res Function(SpotubeAlbumObject) then) = - _$SpotubeAlbumObjectCopyWithImpl<$Res, SpotubeAlbumObject>; +abstract class $SpotubeFullAlbumObjectCopyWith<$Res> { + factory $SpotubeFullAlbumObjectCopyWith(SpotubeFullAlbumObject value, + $Res Function(SpotubeFullAlbumObject) then) = + _$SpotubeFullAlbumObjectCopyWithImpl<$Res, SpotubeFullAlbumObject>; @useResult $Res call( - {String uid, - String title, - SpotubeArtistObject artist, + {String id, + String name, + List artists, List images, String releaseDate, - String externalUrl, - SpotubeAlbumType type}); - - $SpotubeArtistObjectCopyWith<$Res> get artist; + String externalUri, + int totalTracks, + SpotubeAlbumType albumType, + String? recordLabel, + List? genres}); } /// @nodoc -class _$SpotubeAlbumObjectCopyWithImpl<$Res, $Val extends SpotubeAlbumObject> - implements $SpotubeAlbumObjectCopyWith<$Res> { - _$SpotubeAlbumObjectCopyWithImpl(this._value, this._then); +class _$SpotubeFullAlbumObjectCopyWithImpl<$Res, + $Val extends SpotubeFullAlbumObject> + implements $SpotubeFullAlbumObjectCopyWith<$Res> { + _$SpotubeFullAlbumObjectCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeAlbumObject + /// Create a copy of SpotubeFullAlbumObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, - Object? title = null, - Object? artist = null, + Object? id = null, + Object? name = null, + Object? artists = null, Object? images = null, Object? releaseDate = null, - Object? externalUrl = null, - Object? type = null, + Object? externalUri = null, + Object? totalTracks = null, + Object? albumType = null, + Object? recordLabel = freezed, + Object? genres = freezed, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable as String, - artist: null == artist - ? _value.artist - : artist // ignore: cast_nullable_to_non_nullable - as SpotubeArtistObject, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, images: null == images ? _value.images : images // ignore: cast_nullable_to_non_nullable @@ -100,83 +110,91 @@ class _$SpotubeAlbumObjectCopyWithImpl<$Res, $Val extends SpotubeAlbumObject> ? _value.releaseDate : releaseDate // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable + totalTracks: null == totalTracks + ? _value.totalTracks + : totalTracks // ignore: cast_nullable_to_non_nullable + as int, + albumType: null == albumType + ? _value.albumType + : albumType // ignore: cast_nullable_to_non_nullable as SpotubeAlbumType, + recordLabel: freezed == recordLabel + ? _value.recordLabel + : recordLabel // ignore: cast_nullable_to_non_nullable + as String?, + genres: freezed == genres + ? _value.genres + : genres // ignore: cast_nullable_to_non_nullable + as List?, ) as $Val); } - - /// Create a copy of SpotubeAlbumObject - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SpotubeArtistObjectCopyWith<$Res> get artist { - return $SpotubeArtistObjectCopyWith<$Res>(_value.artist, (value) { - return _then(_value.copyWith(artist: value) as $Val); - }); - } } /// @nodoc -abstract class _$$SpotubeAlbumObjectImplCopyWith<$Res> - implements $SpotubeAlbumObjectCopyWith<$Res> { - factory _$$SpotubeAlbumObjectImplCopyWith(_$SpotubeAlbumObjectImpl value, - $Res Function(_$SpotubeAlbumObjectImpl) then) = - __$$SpotubeAlbumObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeFullAlbumObjectImplCopyWith<$Res> + implements $SpotubeFullAlbumObjectCopyWith<$Res> { + factory _$$SpotubeFullAlbumObjectImplCopyWith( + _$SpotubeFullAlbumObjectImpl value, + $Res Function(_$SpotubeFullAlbumObjectImpl) then) = + __$$SpotubeFullAlbumObjectImplCopyWithImpl<$Res>; @override @useResult $Res call( - {String uid, - String title, - SpotubeArtistObject artist, + {String id, + String name, + List artists, List images, String releaseDate, - String externalUrl, - SpotubeAlbumType type}); - - @override - $SpotubeArtistObjectCopyWith<$Res> get artist; + String externalUri, + int totalTracks, + SpotubeAlbumType albumType, + String? recordLabel, + List? genres}); } /// @nodoc -class __$$SpotubeAlbumObjectImplCopyWithImpl<$Res> - extends _$SpotubeAlbumObjectCopyWithImpl<$Res, _$SpotubeAlbumObjectImpl> - implements _$$SpotubeAlbumObjectImplCopyWith<$Res> { - __$$SpotubeAlbumObjectImplCopyWithImpl(_$SpotubeAlbumObjectImpl _value, - $Res Function(_$SpotubeAlbumObjectImpl) _then) +class __$$SpotubeFullAlbumObjectImplCopyWithImpl<$Res> + extends _$SpotubeFullAlbumObjectCopyWithImpl<$Res, + _$SpotubeFullAlbumObjectImpl> + implements _$$SpotubeFullAlbumObjectImplCopyWith<$Res> { + __$$SpotubeFullAlbumObjectImplCopyWithImpl( + _$SpotubeFullAlbumObjectImpl _value, + $Res Function(_$SpotubeFullAlbumObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeAlbumObject + /// Create a copy of SpotubeFullAlbumObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, - Object? title = null, - Object? artist = null, + Object? id = null, + Object? name = null, + Object? artists = null, Object? images = null, Object? releaseDate = null, - Object? externalUrl = null, - Object? type = null, + Object? externalUri = null, + Object? totalTracks = null, + Object? albumType = null, + Object? recordLabel = freezed, + Object? genres = freezed, }) { - return _then(_$SpotubeAlbumObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + return _then(_$SpotubeFullAlbumObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable as String, - artist: null == artist - ? _value.artist - : artist // ignore: cast_nullable_to_non_nullable - as SpotubeArtistObject, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, images: null == images ? _value._images : images // ignore: cast_nullable_to_non_nullable @@ -185,40 +203,63 @@ class __$$SpotubeAlbumObjectImplCopyWithImpl<$Res> ? _value.releaseDate : releaseDate // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, - type: null == type - ? _value.type - : type // ignore: cast_nullable_to_non_nullable + totalTracks: null == totalTracks + ? _value.totalTracks + : totalTracks // ignore: cast_nullable_to_non_nullable + as int, + albumType: null == albumType + ? _value.albumType + : albumType // ignore: cast_nullable_to_non_nullable as SpotubeAlbumType, + recordLabel: freezed == recordLabel + ? _value.recordLabel + : recordLabel // ignore: cast_nullable_to_non_nullable + as String?, + genres: freezed == genres + ? _value._genres + : genres // ignore: cast_nullable_to_non_nullable + as List?, )); } } /// @nodoc @JsonSerializable() -class _$SpotubeAlbumObjectImpl implements _SpotubeAlbumObject { - _$SpotubeAlbumObjectImpl( - {required this.uid, - required this.title, - required this.artist, +class _$SpotubeFullAlbumObjectImpl implements _SpotubeFullAlbumObject { + _$SpotubeFullAlbumObjectImpl( + {required this.id, + required this.name, + required final List artists, final List images = const [], required this.releaseDate, - required this.externalUrl, - required this.type}) - : _images = images; + required this.externalUri, + required this.totalTracks, + required this.albumType, + this.recordLabel, + final List? genres}) + : _artists = artists, + _images = images, + _genres = genres; - factory _$SpotubeAlbumObjectImpl.fromJson(Map json) => - _$$SpotubeAlbumObjectImplFromJson(json); + factory _$SpotubeFullAlbumObjectImpl.fromJson(Map json) => + _$$SpotubeFullAlbumObjectImplFromJson(json); @override - final String uid; + final String id; @override - final String title; + final String name; + final List _artists; @override - final SpotubeArtistObject artist; + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + final List _images; @override @JsonKey() @@ -231,242 +272,332 @@ class _$SpotubeAlbumObjectImpl implements _SpotubeAlbumObject { @override final String releaseDate; @override - final String externalUrl; + final String externalUri; @override - final SpotubeAlbumType type; + final int totalTracks; + @override + final SpotubeAlbumType albumType; + @override + final String? recordLabel; + final List? _genres; + @override + List? get genres { + final value = _genres; + if (value == null) return null; + if (_genres is EqualUnmodifiableListView) return _genres; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } @override String toString() { - return 'SpotubeAlbumObject(uid: $uid, title: $title, artist: $artist, images: $images, releaseDate: $releaseDate, externalUrl: $externalUrl, type: $type)'; + return 'SpotubeFullAlbumObject(id: $id, name: $name, artists: $artists, images: $images, releaseDate: $releaseDate, externalUri: $externalUri, totalTracks: $totalTracks, albumType: $albumType, recordLabel: $recordLabel, genres: $genres)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubeAlbumObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && - (identical(other.title, title) || other.title == title) && - (identical(other.artist, artist) || other.artist == artist) && + other is _$SpotubeFullAlbumObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + const DeepCollectionEquality().equals(other._artists, _artists) && const DeepCollectionEquality().equals(other._images, _images) && (identical(other.releaseDate, releaseDate) || other.releaseDate == releaseDate) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl) && - (identical(other.type, type) || other.type == type)); + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.totalTracks, totalTracks) || + other.totalTracks == totalTracks) && + (identical(other.albumType, albumType) || + other.albumType == albumType) && + (identical(other.recordLabel, recordLabel) || + other.recordLabel == recordLabel) && + const DeepCollectionEquality().equals(other._genres, _genres)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, - uid, - title, - artist, + id, + name, + const DeepCollectionEquality().hash(_artists), const DeepCollectionEquality().hash(_images), releaseDate, - externalUrl, - type); + externalUri, + totalTracks, + albumType, + recordLabel, + const DeepCollectionEquality().hash(_genres)); - /// Create a copy of SpotubeAlbumObject + /// Create a copy of SpotubeFullAlbumObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$SpotubeAlbumObjectImplCopyWith<_$SpotubeAlbumObjectImpl> get copyWith => - __$$SpotubeAlbumObjectImplCopyWithImpl<_$SpotubeAlbumObjectImpl>( - this, _$identity); + _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> + get copyWith => __$$SpotubeFullAlbumObjectImplCopyWithImpl< + _$SpotubeFullAlbumObjectImpl>(this, _$identity); @override Map toJson() { - return _$$SpotubeAlbumObjectImplToJson( + return _$$SpotubeFullAlbumObjectImplToJson( this, ); } } -abstract class _SpotubeAlbumObject implements SpotubeAlbumObject { - factory _SpotubeAlbumObject( - {required final String uid, - required final String title, - required final SpotubeArtistObject artist, +abstract class _SpotubeFullAlbumObject implements SpotubeFullAlbumObject { + factory _SpotubeFullAlbumObject( + {required final String id, + required final String name, + required final List artists, final List images, required final String releaseDate, - required final String externalUrl, - required final SpotubeAlbumType type}) = _$SpotubeAlbumObjectImpl; + required final String externalUri, + required final int totalTracks, + required final SpotubeAlbumType albumType, + final String? recordLabel, + final List? genres}) = _$SpotubeFullAlbumObjectImpl; - factory _SpotubeAlbumObject.fromJson(Map json) = - _$SpotubeAlbumObjectImpl.fromJson; + factory _SpotubeFullAlbumObject.fromJson(Map json) = + _$SpotubeFullAlbumObjectImpl.fromJson; @override - String get uid; + String get id; @override - String get title; + String get name; @override - SpotubeArtistObject get artist; + List get artists; @override List get images; @override String get releaseDate; @override - String get externalUrl; + String get externalUri; @override - SpotubeAlbumType get type; + int get totalTracks; + @override + SpotubeAlbumType get albumType; + @override + String? get recordLabel; + @override + List? get genres; - /// Create a copy of SpotubeAlbumObject + /// Create a copy of SpotubeFullAlbumObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeAlbumObjectImplCopyWith<_$SpotubeAlbumObjectImpl> get copyWith => - throw _privateConstructorUsedError; + _$$SpotubeFullAlbumObjectImplCopyWith<_$SpotubeFullAlbumObjectImpl> + get copyWith => throw _privateConstructorUsedError; } -SpotubeArtistObject _$SpotubeArtistObjectFromJson(Map json) { - return _SpotubeArtistObject.fromJson(json); +SpotubeSimpleAlbumObject _$SpotubeSimpleAlbumObjectFromJson( + Map json) { + return _SpotubeSimpleAlbumObject.fromJson(json); } /// @nodoc -mixin _$SpotubeArtistObject { - String get uid => throw _privateConstructorUsedError; +mixin _$SpotubeSimpleAlbumObject { + String get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + List get artists => + throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; + String get releaseDate => throw _privateConstructorUsedError; + SpotubeAlbumType get albumType => throw _privateConstructorUsedError; - /// Serializes this SpotubeArtistObject to a JSON map. + /// Serializes this SpotubeSimpleAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of SpotubeArtistObject + /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubeArtistObjectCopyWith get copyWith => + $SpotubeSimpleAlbumObjectCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubeArtistObjectCopyWith<$Res> { - factory $SpotubeArtistObjectCopyWith( - SpotubeArtistObject value, $Res Function(SpotubeArtistObject) then) = - _$SpotubeArtistObjectCopyWithImpl<$Res, SpotubeArtistObject>; +abstract class $SpotubeSimpleAlbumObjectCopyWith<$Res> { + factory $SpotubeSimpleAlbumObjectCopyWith(SpotubeSimpleAlbumObject value, + $Res Function(SpotubeSimpleAlbumObject) then) = + _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, SpotubeSimpleAlbumObject>; @useResult $Res call( - {String uid, + {String id, String name, + String externalUri, + List artists, List images, - String externalUrl}); + String releaseDate, + SpotubeAlbumType albumType}); } /// @nodoc -class _$SpotubeArtistObjectCopyWithImpl<$Res, $Val extends SpotubeArtistObject> - implements $SpotubeArtistObjectCopyWith<$Res> { - _$SpotubeArtistObjectCopyWithImpl(this._value, this._then); +class _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, + $Val extends SpotubeSimpleAlbumObject> + implements $SpotubeSimpleAlbumObjectCopyWith<$Res> { + _$SpotubeSimpleAlbumObjectCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeArtistObject + /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, + Object? externalUri = null, + Object? artists = null, Object? images = null, - Object? externalUrl = null, + Object? releaseDate = null, + Object? albumType = null, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + 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, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, images: null == images ? _value.images : images // ignore: cast_nullable_to_non_nullable as List, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + releaseDate: null == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable as String, + albumType: null == albumType + ? _value.albumType + : albumType // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumType, ) as $Val); } } /// @nodoc -abstract class _$$SpotubeArtistObjectImplCopyWith<$Res> - implements $SpotubeArtistObjectCopyWith<$Res> { - factory _$$SpotubeArtistObjectImplCopyWith(_$SpotubeArtistObjectImpl value, - $Res Function(_$SpotubeArtistObjectImpl) then) = - __$$SpotubeArtistObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeSimpleAlbumObjectImplCopyWith<$Res> + implements $SpotubeSimpleAlbumObjectCopyWith<$Res> { + factory _$$SpotubeSimpleAlbumObjectImplCopyWith( + _$SpotubeSimpleAlbumObjectImpl value, + $Res Function(_$SpotubeSimpleAlbumObjectImpl) then) = + __$$SpotubeSimpleAlbumObjectImplCopyWithImpl<$Res>; @override @useResult $Res call( - {String uid, + {String id, String name, + String externalUri, + List artists, List images, - String externalUrl}); + String releaseDate, + SpotubeAlbumType albumType}); } /// @nodoc -class __$$SpotubeArtistObjectImplCopyWithImpl<$Res> - extends _$SpotubeArtistObjectCopyWithImpl<$Res, _$SpotubeArtistObjectImpl> - implements _$$SpotubeArtistObjectImplCopyWith<$Res> { - __$$SpotubeArtistObjectImplCopyWithImpl(_$SpotubeArtistObjectImpl _value, - $Res Function(_$SpotubeArtistObjectImpl) _then) +class __$$SpotubeSimpleAlbumObjectImplCopyWithImpl<$Res> + extends _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, + _$SpotubeSimpleAlbumObjectImpl> + implements _$$SpotubeSimpleAlbumObjectImplCopyWith<$Res> { + __$$SpotubeSimpleAlbumObjectImplCopyWithImpl( + _$SpotubeSimpleAlbumObjectImpl _value, + $Res Function(_$SpotubeSimpleAlbumObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeArtistObject + /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, + Object? externalUri = null, + Object? artists = null, Object? images = null, - Object? externalUrl = null, + Object? releaseDate = null, + Object? albumType = null, }) { - return _then(_$SpotubeArtistObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + return _then(_$SpotubeSimpleAlbumObjectImpl( + 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, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, images: null == images ? _value._images : images // ignore: cast_nullable_to_non_nullable as List, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + releaseDate: null == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable as String, + albumType: null == albumType + ? _value.albumType + : albumType // ignore: cast_nullable_to_non_nullable + as SpotubeAlbumType, )); } } /// @nodoc @JsonSerializable() -class _$SpotubeArtistObjectImpl implements _SpotubeArtistObject { - _$SpotubeArtistObjectImpl( - {required this.uid, +class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { + _$SpotubeSimpleAlbumObjectImpl( + {required this.id, required this.name, + required this.externalUri, + required final List artists, final List images = const [], - required this.externalUrl}) - : _images = images; + required this.releaseDate, + required this.albumType}) + : _artists = artists, + _images = images; - factory _$SpotubeArtistObjectImpl.fromJson(Map json) => - _$$SpotubeArtistObjectImplFromJson(json); + factory _$SpotubeSimpleAlbumObjectImpl.fromJson(Map json) => + _$$SpotubeSimpleAlbumObjectImplFromJson(json); @override - final String uid; + final String id; @override final String name; + @override + final String externalUri; + final List _artists; + @override + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + final List _images; @override @JsonKey() @@ -477,222 +608,280 @@ class _$SpotubeArtistObjectImpl implements _SpotubeArtistObject { } @override - final String externalUrl; + final String releaseDate; + @override + final SpotubeAlbumType albumType; @override String toString() { - return 'SpotubeArtistObject(uid: $uid, name: $name, images: $images, externalUrl: $externalUrl)'; + return 'SpotubeSimpleAlbumObject(id: $id, name: $name, externalUri: $externalUri, artists: $artists, images: $images, releaseDate: $releaseDate, albumType: $albumType)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubeArtistObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && + other is _$SpotubeSimpleAlbumObjectImpl && + (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + const DeepCollectionEquality().equals(other._artists, _artists) && const DeepCollectionEquality().equals(other._images, _images) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl)); + (identical(other.releaseDate, releaseDate) || + other.releaseDate == releaseDate) && + (identical(other.albumType, albumType) || + other.albumType == albumType)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, uid, name, - const DeepCollectionEquality().hash(_images), externalUrl); + int get hashCode => Object.hash( + runtimeType, + id, + name, + externalUri, + const DeepCollectionEquality().hash(_artists), + const DeepCollectionEquality().hash(_images), + releaseDate, + albumType); - /// Create a copy of SpotubeArtistObject + /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$SpotubeArtistObjectImplCopyWith<_$SpotubeArtistObjectImpl> get copyWith => - __$$SpotubeArtistObjectImplCopyWithImpl<_$SpotubeArtistObjectImpl>( - this, _$identity); + _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> + get copyWith => __$$SpotubeSimpleAlbumObjectImplCopyWithImpl< + _$SpotubeSimpleAlbumObjectImpl>(this, _$identity); @override Map toJson() { - return _$$SpotubeArtistObjectImplToJson( + return _$$SpotubeSimpleAlbumObjectImplToJson( this, ); } } -abstract class _SpotubeArtistObject implements SpotubeArtistObject { - factory _SpotubeArtistObject( - {required final String uid, - required final String name, - final List images, - required final String externalUrl}) = _$SpotubeArtistObjectImpl; +abstract class _SpotubeSimpleAlbumObject implements SpotubeSimpleAlbumObject { + factory _SpotubeSimpleAlbumObject( + {required final String id, + required final String name, + required final String externalUri, + required final List artists, + final List images, + required final String releaseDate, + required final SpotubeAlbumType albumType}) = + _$SpotubeSimpleAlbumObjectImpl; - factory _SpotubeArtistObject.fromJson(Map json) = - _$SpotubeArtistObjectImpl.fromJson; + factory _SpotubeSimpleAlbumObject.fromJson(Map json) = + _$SpotubeSimpleAlbumObjectImpl.fromJson; @override - String get uid; + String get id; @override String get name; @override + String get externalUri; + @override + List get artists; + @override List get images; @override - String get externalUrl; + String get releaseDate; + @override + SpotubeAlbumType get albumType; - /// Create a copy of SpotubeArtistObject + /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeArtistObjectImplCopyWith<_$SpotubeArtistObjectImpl> get copyWith => - throw _privateConstructorUsedError; + _$$SpotubeSimpleAlbumObjectImplCopyWith<_$SpotubeSimpleAlbumObjectImpl> + get copyWith => throw _privateConstructorUsedError; } -SpotubeFeedObject _$SpotubeFeedObjectFromJson(Map json) { - return _SpotubeFeedObject.fromJson(json); +SpotubeFullArtistObject _$SpotubeFullArtistObjectFromJson( + Map json) { + return _SpotubeFullArtistObject.fromJson(json); } /// @nodoc -mixin _$SpotubeFeedObject { - String get uid => throw _privateConstructorUsedError; +mixin _$SpotubeFullArtistObject { + String get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; + List? get genres => throw _privateConstructorUsedError; + int? get followers => throw _privateConstructorUsedError; - /// Serializes this SpotubeFeedObject to a JSON map. + /// Serializes this SpotubeFullArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of SpotubeFeedObject + /// Create a copy of SpotubeFullArtistObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubeFeedObjectCopyWith get copyWith => + $SpotubeFullArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubeFeedObjectCopyWith<$Res> { - factory $SpotubeFeedObjectCopyWith( - SpotubeFeedObject value, $Res Function(SpotubeFeedObject) then) = - _$SpotubeFeedObjectCopyWithImpl<$Res, SpotubeFeedObject>; +abstract class $SpotubeFullArtistObjectCopyWith<$Res> { + factory $SpotubeFullArtistObjectCopyWith(SpotubeFullArtistObject value, + $Res Function(SpotubeFullArtistObject) then) = + _$SpotubeFullArtistObjectCopyWithImpl<$Res, SpotubeFullArtistObject>; @useResult $Res call( - {String uid, + {String id, String name, - String externalUrl, - List images}); + String externalUri, + List images, + List? genres, + int? followers}); } /// @nodoc -class _$SpotubeFeedObjectCopyWithImpl<$Res, $Val extends SpotubeFeedObject> - implements $SpotubeFeedObjectCopyWith<$Res> { - _$SpotubeFeedObjectCopyWithImpl(this._value, this._then); +class _$SpotubeFullArtistObjectCopyWithImpl<$Res, + $Val extends SpotubeFullArtistObject> + implements $SpotubeFullArtistObjectCopyWith<$Res> { + _$SpotubeFullArtistObjectCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeFeedObject + /// Create a copy of SpotubeFullArtistObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? externalUrl = null, + Object? externalUri = null, Object? images = null, + Object? genres = freezed, + Object? followers = freezed, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + 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, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, images: null == images ? _value.images : images // ignore: cast_nullable_to_non_nullable as List, + genres: freezed == genres + ? _value.genres + : genres // ignore: cast_nullable_to_non_nullable + as List?, + followers: freezed == followers + ? _value.followers + : followers // ignore: cast_nullable_to_non_nullable + as int?, ) as $Val); } } /// @nodoc -abstract class _$$SpotubeFeedObjectImplCopyWith<$Res> - implements $SpotubeFeedObjectCopyWith<$Res> { - factory _$$SpotubeFeedObjectImplCopyWith(_$SpotubeFeedObjectImpl value, - $Res Function(_$SpotubeFeedObjectImpl) then) = - __$$SpotubeFeedObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeFullArtistObjectImplCopyWith<$Res> + implements $SpotubeFullArtistObjectCopyWith<$Res> { + factory _$$SpotubeFullArtistObjectImplCopyWith( + _$SpotubeFullArtistObjectImpl value, + $Res Function(_$SpotubeFullArtistObjectImpl) then) = + __$$SpotubeFullArtistObjectImplCopyWithImpl<$Res>; @override @useResult $Res call( - {String uid, + {String id, String name, - String externalUrl, - List images}); + String externalUri, + List images, + List? genres, + int? followers}); } /// @nodoc -class __$$SpotubeFeedObjectImplCopyWithImpl<$Res> - extends _$SpotubeFeedObjectCopyWithImpl<$Res, _$SpotubeFeedObjectImpl> - implements _$$SpotubeFeedObjectImplCopyWith<$Res> { - __$$SpotubeFeedObjectImplCopyWithImpl(_$SpotubeFeedObjectImpl _value, - $Res Function(_$SpotubeFeedObjectImpl) _then) +class __$$SpotubeFullArtistObjectImplCopyWithImpl<$Res> + extends _$SpotubeFullArtistObjectCopyWithImpl<$Res, + _$SpotubeFullArtistObjectImpl> + implements _$$SpotubeFullArtistObjectImplCopyWith<$Res> { + __$$SpotubeFullArtistObjectImplCopyWithImpl( + _$SpotubeFullArtistObjectImpl _value, + $Res Function(_$SpotubeFullArtistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeFeedObject + /// Create a copy of SpotubeFullArtistObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? externalUrl = null, + Object? externalUri = null, Object? images = null, + Object? genres = freezed, + Object? followers = freezed, }) { - return _then(_$SpotubeFeedObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + return _then(_$SpotubeFullArtistObjectImpl( + 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, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, images: null == images ? _value._images : images // ignore: cast_nullable_to_non_nullable as List, + genres: freezed == genres + ? _value._genres + : genres // ignore: cast_nullable_to_non_nullable + as List?, + followers: freezed == followers + ? _value.followers + : followers // ignore: cast_nullable_to_non_nullable + as int?, )); } } /// @nodoc @JsonSerializable() -class _$SpotubeFeedObjectImpl implements _SpotubeFeedObject { - _$SpotubeFeedObjectImpl( - {required this.uid, +class _$SpotubeFullArtistObjectImpl implements _SpotubeFullArtistObject { + _$SpotubeFullArtistObjectImpl( + {required this.id, required this.name, - required this.externalUrl, - final List images = const []}) - : _images = images; + required this.externalUri, + final List images = const [], + final List? genres, + this.followers}) + : _images = images, + _genres = genres; - factory _$SpotubeFeedObjectImpl.fromJson(Map json) => - _$$SpotubeFeedObjectImplFromJson(json); + factory _$SpotubeFullArtistObjectImpl.fromJson(Map json) => + _$$SpotubeFullArtistObjectImplFromJson(json); @override - final String uid; + final String id; @override final String name; @override - final String externalUrl; + final String externalUri; final List _images; @override @JsonKey() @@ -702,72 +891,1217 @@ class _$SpotubeFeedObjectImpl implements _SpotubeFeedObject { return EqualUnmodifiableListView(_images); } + final List? _genres; + @override + List? get genres { + final value = _genres; + if (value == null) return null; + if (_genres is EqualUnmodifiableListView) return _genres; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } + + @override + final int? followers; + @override String toString() { - return 'SpotubeFeedObject(uid: $uid, name: $name, externalUrl: $externalUrl, images: $images)'; + return 'SpotubeFullArtistObject(id: $id, name: $name, externalUri: $externalUri, images: $images, genres: $genres, followers: $followers)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubeFeedObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && + other is _$SpotubeFullArtistObjectImpl && + (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl) && - const DeepCollectionEquality().equals(other._images, _images)); + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + const DeepCollectionEquality().equals(other._images, _images) && + const DeepCollectionEquality().equals(other._genres, _genres) && + (identical(other.followers, followers) || + other.followers == followers)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, uid, name, externalUrl, - const DeepCollectionEquality().hash(_images)); + int get hashCode => Object.hash( + runtimeType, + id, + name, + externalUri, + const DeepCollectionEquality().hash(_images), + const DeepCollectionEquality().hash(_genres), + followers); - /// Create a copy of SpotubeFeedObject + /// Create a copy of SpotubeFullArtistObject /// 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); + _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> + get copyWith => __$$SpotubeFullArtistObjectImplCopyWithImpl< + _$SpotubeFullArtistObjectImpl>(this, _$identity); @override Map toJson() { - return _$$SpotubeFeedObjectImplToJson( + return _$$SpotubeFullArtistObjectImplToJson( this, ); } } -abstract class _SpotubeFeedObject implements SpotubeFeedObject { - factory _SpotubeFeedObject( - {required final String uid, +abstract class _SpotubeFullArtistObject implements SpotubeFullArtistObject { + factory _SpotubeFullArtistObject( + {required final String id, required final String name, - required final String externalUrl, - final List images}) = _$SpotubeFeedObjectImpl; + required final String externalUri, + final List images, + final List? genres, + final int? followers}) = _$SpotubeFullArtistObjectImpl; - factory _SpotubeFeedObject.fromJson(Map json) = - _$SpotubeFeedObjectImpl.fromJson; + factory _SpotubeFullArtistObject.fromJson(Map json) = + _$SpotubeFullArtistObjectImpl.fromJson; @override - String get uid; + String get id; @override String get name; @override - String get externalUrl; + String get externalUri; @override List get images; + @override + List? get genres; + @override + int? get followers; - /// Create a copy of SpotubeFeedObject + /// Create a copy of SpotubeFullArtistObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeFeedObjectImplCopyWith<_$SpotubeFeedObjectImpl> get copyWith => + _$$SpotubeFullArtistObjectImplCopyWith<_$SpotubeFullArtistObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeSimpleArtistObject _$SpotubeSimpleArtistObjectFromJson( + Map json) { + return _SpotubeSimpleArtistObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeSimpleArtistObject { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + + /// Serializes this SpotubeSimpleArtistObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeSimpleArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeSimpleArtistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } +/// @nodoc +abstract class $SpotubeSimpleArtistObjectCopyWith<$Res> { + factory $SpotubeSimpleArtistObjectCopyWith(SpotubeSimpleArtistObject value, + $Res Function(SpotubeSimpleArtistObject) then) = + _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, SpotubeSimpleArtistObject>; + @useResult + $Res call({String id, String name, String externalUri}); +} + +/// @nodoc +class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, + $Val extends SpotubeSimpleArtistObject> + implements $SpotubeSimpleArtistObjectCopyWith<$Res> { + _$SpotubeSimpleArtistObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeSimpleArtistObject + /// 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, + }) { + 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, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeSimpleArtistObjectImplCopyWith<$Res> + implements $SpotubeSimpleArtistObjectCopyWith<$Res> { + factory _$$SpotubeSimpleArtistObjectImplCopyWith( + _$SpotubeSimpleArtistObjectImpl value, + $Res Function(_$SpotubeSimpleArtistObjectImpl) then) = + __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String id, String name, String externalUri}); +} + +/// @nodoc +class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res> + extends _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, + _$SpotubeSimpleArtistObjectImpl> + implements _$$SpotubeSimpleArtistObjectImplCopyWith<$Res> { + __$$SpotubeSimpleArtistObjectImplCopyWithImpl( + _$SpotubeSimpleArtistObjectImpl _value, + $Res Function(_$SpotubeSimpleArtistObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeSimpleArtistObject + /// 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, + }) { + return _then(_$SpotubeSimpleArtistObjectImpl( + 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, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { + _$SpotubeSimpleArtistObjectImpl( + {required this.id, required this.name, required this.externalUri}); + + factory _$SpotubeSimpleArtistObjectImpl.fromJson(Map json) => + _$$SpotubeSimpleArtistObjectImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + final String externalUri; + + @override + String toString() { + return 'SpotubeSimpleArtistObject(id: $id, name: $name, externalUri: $externalUri)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeSimpleArtistObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, externalUri); + + /// Create a copy of SpotubeSimpleArtistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> + get copyWith => __$$SpotubeSimpleArtistObjectImplCopyWithImpl< + _$SpotubeSimpleArtistObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeSimpleArtistObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject { + factory _SpotubeSimpleArtistObject( + {required final String id, + required final String name, + required final String externalUri}) = _$SpotubeSimpleArtistObjectImpl; + + factory _SpotubeSimpleArtistObject.fromJson(Map json) = + _$SpotubeSimpleArtistObjectImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + String get externalUri; + + /// Create a copy of SpotubeSimpleArtistObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeSimpleArtistObjectImplCopyWith<_$SpotubeSimpleArtistObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeBrowseSectionObject _$SpotubeBrowseSectionObjectFromJson( + Map json) { + switch (json['itemType']) { + case 'Album': + return SpotubeBrowseAlbumSectionObject.fromJson(json); + case 'Artist': + return SpotubeBrowseArtistSectionObject.fromJson(json); + case 'Playlist': + return SpotubeBrowsePlaylistSectionObject.fromJson(json); + + default: + throw CheckedFromJsonException( + json, + 'itemType', + 'SpotubeBrowseSectionObject', + 'Invalid union type "${json['itemType']}"!'); + } +} + +/// @nodoc +mixin _$SpotubeBrowseSectionObject { + String get id => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + SectionItemType get itemType => throw _privateConstructorUsedError; + List get items => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + album, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + artist, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + playlist, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeBrowseAlbumSectionObject value) album, + required TResult Function(SpotubeBrowseArtistSectionObject value) artist, + required TResult Function(SpotubeBrowsePlaylistSectionObject value) + playlist, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Serializes this SpotubeBrowseSectionObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeBrowseSectionObjectCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeBrowseSectionObjectCopyWith<$Res> { + factory $SpotubeBrowseSectionObjectCopyWith(SpotubeBrowseSectionObject value, + $Res Function(SpotubeBrowseSectionObject) then) = + _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, + SpotubeBrowseSectionObject>; + @useResult + $Res call( + {String id, String title, String externalUri, SectionItemType itemType}); +} + +/// @nodoc +class _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, + $Val extends SpotubeBrowseSectionObject> + implements $SpotubeBrowseSectionObjectCopyWith<$Res> { + _$SpotubeBrowseSectionObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? externalUri = null, + Object? itemType = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + itemType: null == itemType + ? _value.itemType + : itemType // ignore: cast_nullable_to_non_nullable + as SectionItemType, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$SpotubeBrowseAlbumSectionObjectImplCopyWith<$Res> + implements $SpotubeBrowseSectionObjectCopyWith<$Res> { + factory _$$SpotubeBrowseAlbumSectionObjectImplCopyWith( + _$SpotubeBrowseAlbumSectionObjectImpl value, + $Res Function(_$SpotubeBrowseAlbumSectionObjectImpl) then) = + __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + String externalUri, + SectionItemType itemType, + List items}); +} + +/// @nodoc +class __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res> + extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, + _$SpotubeBrowseAlbumSectionObjectImpl> + implements _$$SpotubeBrowseAlbumSectionObjectImplCopyWith<$Res> { + __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl( + _$SpotubeBrowseAlbumSectionObjectImpl _value, + $Res Function(_$SpotubeBrowseAlbumSectionObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? externalUri = null, + Object? itemType = null, + Object? items = null, + }) { + return _then(_$SpotubeBrowseAlbumSectionObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + itemType: null == itemType + ? _value.itemType + : itemType // ignore: cast_nullable_to_non_nullable + as SectionItemType, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeBrowseAlbumSectionObjectImpl + implements SpotubeBrowseAlbumSectionObject { + _$SpotubeBrowseAlbumSectionObjectImpl( + {required this.id, + required this.title, + required this.externalUri, + required this.itemType, + required final List items}) + : _items = items; + + factory _$SpotubeBrowseAlbumSectionObjectImpl.fromJson( + Map json) => + _$$SpotubeBrowseAlbumSectionObjectImplFromJson(json); + + @override + final String id; + @override + final String title; + @override + final String externalUri; + @override + final SectionItemType itemType; + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + String toString() { + return 'SpotubeBrowseSectionObject.album(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeBrowseAlbumSectionObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.itemType, itemType) || + other.itemType == itemType) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, + const DeepCollectionEquality().hash(_items)); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeBrowseAlbumSectionObjectImplCopyWith< + _$SpotubeBrowseAlbumSectionObjectImpl> + get copyWith => __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl< + _$SpotubeBrowseAlbumSectionObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + album, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + artist, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + playlist, + }) { + return album(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + }) { + return album?.call(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + required TResult orElse(), + }) { + if (album != null) { + return album(id, title, externalUri, itemType, items); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeBrowseAlbumSectionObject value) album, + required TResult Function(SpotubeBrowseArtistSectionObject value) artist, + required TResult Function(SpotubeBrowsePlaylistSectionObject value) + playlist, + }) { + return album(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + }) { + return album?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + required TResult orElse(), + }) { + if (album != null) { + return album(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeBrowseAlbumSectionObjectImplToJson( + this, + ); + } +} + +abstract class SpotubeBrowseAlbumSectionObject + implements SpotubeBrowseSectionObject { + factory SpotubeBrowseAlbumSectionObject( + {required final String id, + required final String title, + required final String externalUri, + required final SectionItemType itemType, + required final List items}) = + _$SpotubeBrowseAlbumSectionObjectImpl; + + factory SpotubeBrowseAlbumSectionObject.fromJson(Map json) = + _$SpotubeBrowseAlbumSectionObjectImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + String get externalUri; + @override + SectionItemType get itemType; + @override + List get items; + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeBrowseAlbumSectionObjectImplCopyWith< + _$SpotubeBrowseAlbumSectionObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SpotubeBrowseArtistSectionObjectImplCopyWith<$Res> + implements $SpotubeBrowseSectionObjectCopyWith<$Res> { + factory _$$SpotubeBrowseArtistSectionObjectImplCopyWith( + _$SpotubeBrowseArtistSectionObjectImpl value, + $Res Function(_$SpotubeBrowseArtistSectionObjectImpl) then) = + __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + String externalUri, + SectionItemType itemType, + List items}); +} + +/// @nodoc +class __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl<$Res> + extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, + _$SpotubeBrowseArtistSectionObjectImpl> + implements _$$SpotubeBrowseArtistSectionObjectImplCopyWith<$Res> { + __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl( + _$SpotubeBrowseArtistSectionObjectImpl _value, + $Res Function(_$SpotubeBrowseArtistSectionObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? externalUri = null, + Object? itemType = null, + Object? items = null, + }) { + return _then(_$SpotubeBrowseArtistSectionObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + itemType: null == itemType + ? _value.itemType + : itemType // ignore: cast_nullable_to_non_nullable + as SectionItemType, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeBrowseArtistSectionObjectImpl + implements SpotubeBrowseArtistSectionObject { + _$SpotubeBrowseArtistSectionObjectImpl( + {required this.id, + required this.title, + required this.externalUri, + required this.itemType, + required final List items}) + : _items = items; + + factory _$SpotubeBrowseArtistSectionObjectImpl.fromJson( + Map json) => + _$$SpotubeBrowseArtistSectionObjectImplFromJson(json); + + @override + final String id; + @override + final String title; + @override + final String externalUri; + @override + final SectionItemType itemType; + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + String toString() { + return 'SpotubeBrowseSectionObject.artist(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeBrowseArtistSectionObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.itemType, itemType) || + other.itemType == itemType) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, + const DeepCollectionEquality().hash(_items)); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeBrowseArtistSectionObjectImplCopyWith< + _$SpotubeBrowseArtistSectionObjectImpl> + get copyWith => __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl< + _$SpotubeBrowseArtistSectionObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + album, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + artist, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + playlist, + }) { + return artist(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + }) { + return artist?.call(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + required TResult orElse(), + }) { + if (artist != null) { + return artist(id, title, externalUri, itemType, items); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeBrowseAlbumSectionObject value) album, + required TResult Function(SpotubeBrowseArtistSectionObject value) artist, + required TResult Function(SpotubeBrowsePlaylistSectionObject value) + playlist, + }) { + return artist(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + }) { + return artist?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + required TResult orElse(), + }) { + if (artist != null) { + return artist(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeBrowseArtistSectionObjectImplToJson( + this, + ); + } +} + +abstract class SpotubeBrowseArtistSectionObject + implements SpotubeBrowseSectionObject { + factory SpotubeBrowseArtistSectionObject( + {required final String id, + required final String title, + required final String externalUri, + required final SectionItemType itemType, + required final List items}) = + _$SpotubeBrowseArtistSectionObjectImpl; + + factory SpotubeBrowseArtistSectionObject.fromJson(Map json) = + _$SpotubeBrowseArtistSectionObjectImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + String get externalUri; + @override + SectionItemType get itemType; + @override + List get items; + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeBrowseArtistSectionObjectImplCopyWith< + _$SpotubeBrowseArtistSectionObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith<$Res> + implements $SpotubeBrowseSectionObjectCopyWith<$Res> { + factory _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith( + _$SpotubeBrowsePlaylistSectionObjectImpl value, + $Res Function(_$SpotubeBrowsePlaylistSectionObjectImpl) then) = + __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + String externalUri, + SectionItemType itemType, + List items}); +} + +/// @nodoc +class __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl<$Res> + extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, + _$SpotubeBrowsePlaylistSectionObjectImpl> + implements _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith<$Res> { + __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl( + _$SpotubeBrowsePlaylistSectionObjectImpl _value, + $Res Function(_$SpotubeBrowsePlaylistSectionObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? externalUri = null, + Object? itemType = null, + Object? items = null, + }) { + return _then(_$SpotubeBrowsePlaylistSectionObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + itemType: null == itemType + ? _value.itemType + : itemType // ignore: cast_nullable_to_non_nullable + as SectionItemType, + items: null == items + ? _value._items + : items // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeBrowsePlaylistSectionObjectImpl + implements SpotubeBrowsePlaylistSectionObject { + _$SpotubeBrowsePlaylistSectionObjectImpl( + {required this.id, + required this.title, + required this.externalUri, + required this.itemType, + required final List items}) + : _items = items; + + factory _$SpotubeBrowsePlaylistSectionObjectImpl.fromJson( + Map json) => + _$$SpotubeBrowsePlaylistSectionObjectImplFromJson(json); + + @override + final String id; + @override + final String title; + @override + final String externalUri; + @override + final SectionItemType itemType; + final List _items; + @override + List get items { + if (_items is EqualUnmodifiableListView) return _items; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_items); + } + + @override + String toString() { + return 'SpotubeBrowseSectionObject.playlist(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeBrowsePlaylistSectionObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.itemType, itemType) || + other.itemType == itemType) && + const DeepCollectionEquality().equals(other._items, _items)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, + const DeepCollectionEquality().hash(_items)); + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith< + _$SpotubeBrowsePlaylistSectionObjectImpl> + get copyWith => __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl< + _$SpotubeBrowsePlaylistSectionObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + album, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + artist, + required TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items) + playlist, + }) { + return playlist(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult? Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + }) { + return playlist?.call(id, title, externalUri, itemType, items); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + album, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + artist, + TResult Function(String id, String title, String externalUri, + SectionItemType itemType, List items)? + playlist, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist(id, title, externalUri, itemType, items); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeBrowseAlbumSectionObject value) album, + required TResult Function(SpotubeBrowseArtistSectionObject value) artist, + required TResult Function(SpotubeBrowsePlaylistSectionObject value) + playlist, + }) { + return playlist(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + }) { + return playlist?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeBrowseAlbumSectionObject value)? album, + TResult Function(SpotubeBrowseArtistSectionObject value)? artist, + TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, + required TResult orElse(), + }) { + if (playlist != null) { + return playlist(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeBrowsePlaylistSectionObjectImplToJson( + this, + ); + } +} + +abstract class SpotubeBrowsePlaylistSectionObject + implements SpotubeBrowseSectionObject { + factory SpotubeBrowsePlaylistSectionObject( + {required final String id, + required final String title, + required final String externalUri, + required final SectionItemType itemType, + required final List items}) = + _$SpotubeBrowsePlaylistSectionObjectImpl; + + factory SpotubeBrowsePlaylistSectionObject.fromJson( + Map json) = + _$SpotubeBrowsePlaylistSectionObjectImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + String get externalUri; + @override + SectionItemType get itemType; + @override + List get items; + + /// Create a copy of SpotubeBrowseSectionObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith< + _$SpotubeBrowsePlaylistSectionObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + SpotubeImageObject _$SpotubeImageObjectFromJson(Map json) { return _SpotubeImageObject.fromJson(json); } @@ -960,9 +2294,9 @@ SpotubePaginationResponseObject _$SpotubePaginationResponseObjectFromJson( /// @nodoc mixin _$SpotubePaginationResponseObject { + int get limit => throw _privateConstructorUsedError; + int? get nextOffset => throw _privateConstructorUsedError; 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; @@ -987,11 +2321,7 @@ abstract class $SpotubePaginationResponseObjectCopyWith { SpotubePaginationResponseObject>; @useResult $Res call( - {int total, - String? nextCursor, - String limit, - bool hasMore, - List items}); + {int limit, int? nextOffset, int total, bool hasMore, List items}); } /// @nodoc @@ -1010,25 +2340,25 @@ class _$SpotubePaginationResponseObjectCopyWithImpl @override @useResult $Res call( - {int total, - String? nextCursor, - String limit, - bool hasMore, - List items}); + {int limit, int? nextOffset, int total, bool hasMore, List items}); } /// @nodoc @@ -1073,25 +2399,25 @@ class __$$SpotubePaginationResponseObjectImplCopyWithImpl @pragma('vm:prefer-inline') @override $Res call({ - Object? total = null, - Object? nextCursor = freezed, Object? limit = null, + Object? nextOffset = freezed, + Object? total = null, Object? hasMore = null, Object? items = null, }) { return _then(_$SpotubePaginationResponseObjectImpl( + limit: null == limit + ? _value.limit + : limit // ignore: cast_nullable_to_non_nullable + as int, + nextOffset: freezed == nextOffset + ? _value.nextOffset + : nextOffset // ignore: cast_nullable_to_non_nullable + as int?, 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 @@ -1109,9 +2435,9 @@ class __$$SpotubePaginationResponseObjectImplCopyWithImpl class _$SpotubePaginationResponseObjectImpl implements _SpotubePaginationResponseObject { _$SpotubePaginationResponseObjectImpl( - {required this.total, - required this.nextCursor, - required this.limit, + {required this.limit, + required this.nextOffset, + required this.total, required this.hasMore, required final List items}) : _items = items; @@ -1120,13 +2446,13 @@ class _$SpotubePaginationResponseObjectImpl Map json, T Function(Object?) fromJsonT) => _$$SpotubePaginationResponseObjectImplFromJson(json, fromJsonT); + @override + final int limit; + @override + final int? nextOffset; @override final int total; @override - final String? nextCursor; - @override - final String limit; - @override final bool hasMore; final List _items; @override @@ -1138,7 +2464,7 @@ class _$SpotubePaginationResponseObjectImpl @override String toString() { - return 'SpotubePaginationResponseObject<$T>(total: $total, nextCursor: $nextCursor, limit: $limit, hasMore: $hasMore, items: $items)'; + return 'SpotubePaginationResponseObject<$T>(limit: $limit, nextOffset: $nextOffset, total: $total, hasMore: $hasMore, items: $items)'; } @override @@ -1146,17 +2472,17 @@ class _$SpotubePaginationResponseObjectImpl 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.nextOffset, nextOffset) || + other.nextOffset == nextOffset) && + (identical(other.total, total) || other.total == total) && (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, + int get hashCode => Object.hash(runtimeType, limit, nextOffset, total, hasMore, const DeepCollectionEquality().hash(_items)); /// Create a copy of SpotubePaginationResponseObject @@ -1178,9 +2504,9 @@ class _$SpotubePaginationResponseObjectImpl abstract class _SpotubePaginationResponseObject implements SpotubePaginationResponseObject { factory _SpotubePaginationResponseObject( - {required final int total, - required final String? nextCursor, - required final String limit, + {required final int limit, + required final int? nextOffset, + required final int total, required final bool hasMore, required final List items}) = _$SpotubePaginationResponseObjectImpl; @@ -1188,13 +2514,13 @@ abstract class _SpotubePaginationResponseObject Map json, T Function(Object?) fromJsonT) = _$SpotubePaginationResponseObjectImpl.fromJson; + @override + int get limit; + @override + int? get nextOffset; @override int get total; @override - String? get nextCursor; - @override - String get limit; - @override bool get hasMore; @override List get items; @@ -1208,107 +2534,121 @@ abstract class _SpotubePaginationResponseObject get copyWith => throw _privateConstructorUsedError; } -SpotubePlaylistObject _$SpotubePlaylistObjectFromJson( +SpotubeFullPlaylistObject _$SpotubeFullPlaylistObjectFromJson( Map json) { - return _SpotubePlaylistObject.fromJson(json); + return _SpotubeFullPlaylistObject.fromJson(json); } /// @nodoc -mixin _$SpotubePlaylistObject { - String get uid => throw _privateConstructorUsedError; +mixin _$SpotubeFullPlaylistObject { + String get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; - List get images => throw _privateConstructorUsedError; String get description => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; SpotubeUserObject get owner => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; List get collaborators => throw _privateConstructorUsedError; + bool get collaborative => throw _privateConstructorUsedError; + bool get public => throw _privateConstructorUsedError; - /// Serializes this SpotubePlaylistObject to a JSON map. + /// Serializes this SpotubeFullPlaylistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubePlaylistObjectCopyWith get copyWith => + $SpotubeFullPlaylistObjectCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubePlaylistObjectCopyWith<$Res> { - factory $SpotubePlaylistObjectCopyWith(SpotubePlaylistObject value, - $Res Function(SpotubePlaylistObject) then) = - _$SpotubePlaylistObjectCopyWithImpl<$Res, SpotubePlaylistObject>; +abstract class $SpotubeFullPlaylistObjectCopyWith<$Res> { + factory $SpotubeFullPlaylistObjectCopyWith(SpotubeFullPlaylistObject value, + $Res Function(SpotubeFullPlaylistObject) then) = + _$SpotubeFullPlaylistObjectCopyWithImpl<$Res, SpotubeFullPlaylistObject>; @useResult $Res call( - {String uid, + {String id, String name, - List images, String description, - String externalUrl, + String externalUri, SpotubeUserObject owner, - List collaborators}); + List images, + List collaborators, + bool collaborative, + bool public}); $SpotubeUserObjectCopyWith<$Res> get owner; } /// @nodoc -class _$SpotubePlaylistObjectCopyWithImpl<$Res, - $Val extends SpotubePlaylistObject> - implements $SpotubePlaylistObjectCopyWith<$Res> { - _$SpotubePlaylistObjectCopyWithImpl(this._value, this._then); +class _$SpotubeFullPlaylistObjectCopyWithImpl<$Res, + $Val extends SpotubeFullPlaylistObject> + implements $SpotubeFullPlaylistObjectCopyWith<$Res> { + _$SpotubeFullPlaylistObjectCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? images = null, Object? description = null, - Object? externalUrl = null, + Object? externalUri = null, Object? owner = null, + Object? images = null, Object? collaborators = null, + Object? collaborative = null, + Object? public = null, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + 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, - images: null == images - ? _value.images - : images // ignore: cast_nullable_to_non_nullable - as List, description: null == description ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, owner: null == owner ? _value.owner : owner // ignore: cast_nullable_to_non_nullable as SpotubeUserObject, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, collaborators: null == collaborators ? _value.collaborators : collaborators // ignore: cast_nullable_to_non_nullable as List, + collaborative: null == collaborative + ? _value.collaborative + : collaborative // ignore: cast_nullable_to_non_nullable + as bool, + public: null == public + ? _value.public + : public // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') @@ -1320,103 +2660,124 @@ class _$SpotubePlaylistObjectCopyWithImpl<$Res, } /// @nodoc -abstract class _$$SpotubePlaylistObjectImplCopyWith<$Res> - implements $SpotubePlaylistObjectCopyWith<$Res> { - factory _$$SpotubePlaylistObjectImplCopyWith( - _$SpotubePlaylistObjectImpl value, - $Res Function(_$SpotubePlaylistObjectImpl) then) = - __$$SpotubePlaylistObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeFullPlaylistObjectImplCopyWith<$Res> + implements $SpotubeFullPlaylistObjectCopyWith<$Res> { + factory _$$SpotubeFullPlaylistObjectImplCopyWith( + _$SpotubeFullPlaylistObjectImpl value, + $Res Function(_$SpotubeFullPlaylistObjectImpl) then) = + __$$SpotubeFullPlaylistObjectImplCopyWithImpl<$Res>; @override @useResult $Res call( - {String uid, + {String id, String name, - List images, String description, - String externalUrl, + String externalUri, SpotubeUserObject owner, - List collaborators}); + List images, + List collaborators, + bool collaborative, + bool public}); @override $SpotubeUserObjectCopyWith<$Res> get owner; } /// @nodoc -class __$$SpotubePlaylistObjectImplCopyWithImpl<$Res> - extends _$SpotubePlaylistObjectCopyWithImpl<$Res, - _$SpotubePlaylistObjectImpl> - implements _$$SpotubePlaylistObjectImplCopyWith<$Res> { - __$$SpotubePlaylistObjectImplCopyWithImpl(_$SpotubePlaylistObjectImpl _value, - $Res Function(_$SpotubePlaylistObjectImpl) _then) +class __$$SpotubeFullPlaylistObjectImplCopyWithImpl<$Res> + extends _$SpotubeFullPlaylistObjectCopyWithImpl<$Res, + _$SpotubeFullPlaylistObjectImpl> + implements _$$SpotubeFullPlaylistObjectImplCopyWith<$Res> { + __$$SpotubeFullPlaylistObjectImplCopyWithImpl( + _$SpotubeFullPlaylistObjectImpl _value, + $Res Function(_$SpotubeFullPlaylistObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, + Object? id = null, Object? name = null, - Object? images = null, Object? description = null, - Object? externalUrl = null, + Object? externalUri = null, Object? owner = null, + Object? images = null, Object? collaborators = null, + Object? collaborative = null, + Object? public = null, }) { - return _then(_$SpotubePlaylistObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + return _then(_$SpotubeFullPlaylistObjectImpl( + 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, - images: null == images - ? _value._images - : images // ignore: cast_nullable_to_non_nullable - as List, description: null == description ? _value.description : description // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable as String, owner: null == owner ? _value.owner : owner // ignore: cast_nullable_to_non_nullable as SpotubeUserObject, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, collaborators: null == collaborators ? _value._collaborators : collaborators // ignore: cast_nullable_to_non_nullable as List, + collaborative: null == collaborative + ? _value.collaborative + : collaborative // ignore: cast_nullable_to_non_nullable + as bool, + public: null == public + ? _value.public + : public // ignore: cast_nullable_to_non_nullable + as bool, )); } } /// @nodoc @JsonSerializable() -class _$SpotubePlaylistObjectImpl implements _SpotubePlaylistObject { - _$SpotubePlaylistObjectImpl( - {required this.uid, +class _$SpotubeFullPlaylistObjectImpl implements _SpotubeFullPlaylistObject { + _$SpotubeFullPlaylistObjectImpl( + {required this.id, required this.name, - final List images = const [], required this.description, - required this.externalUrl, + required this.externalUri, required this.owner, - final List collaborators = const []}) + final List images = const [], + final List collaborators = const [], + this.collaborative = false, + this.public = false}) : _images = images, _collaborators = collaborators; - factory _$SpotubePlaylistObjectImpl.fromJson(Map json) => - _$$SpotubePlaylistObjectImplFromJson(json); + factory _$SpotubeFullPlaylistObjectImpl.fromJson(Map json) => + _$$SpotubeFullPlaylistObjectImplFromJson(json); @override - final String uid; + final String id; @override final String name; + @override + final String description; + @override + final String externalUri; + @override + final SpotubeUserObject owner; final List _images; @override @JsonKey() @@ -1426,12 +2787,6 @@ class _$SpotubePlaylistObjectImpl implements _SpotubePlaylistObject { return EqualUnmodifiableListView(_images); } - @override - final String description; - @override - final String externalUrl; - @override - final SpotubeUserObject owner; final List _collaborators; @override @JsonKey() @@ -1441,91 +2796,398 @@ class _$SpotubePlaylistObjectImpl implements _SpotubePlaylistObject { return EqualUnmodifiableListView(_collaborators); } + @override + @JsonKey() + final bool collaborative; + @override + @JsonKey() + final bool public; + @override String toString() { - return 'SpotubePlaylistObject(uid: $uid, name: $name, images: $images, description: $description, externalUrl: $externalUrl, owner: $owner, collaborators: $collaborators)'; + return 'SpotubeFullPlaylistObject(id: $id, name: $name, description: $description, externalUri: $externalUri, owner: $owner, images: $images, collaborators: $collaborators, collaborative: $collaborative, public: $public)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubePlaylistObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && + other is _$SpotubeFullPlaylistObjectImpl && + (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && - const DeepCollectionEquality().equals(other._images, _images) && (identical(other.description, description) || other.description == description) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && (identical(other.owner, owner) || other.owner == owner) && + const DeepCollectionEquality().equals(other._images, _images) && const DeepCollectionEquality() - .equals(other._collaborators, _collaborators)); + .equals(other._collaborators, _collaborators) && + (identical(other.collaborative, collaborative) || + other.collaborative == collaborative) && + (identical(other.public, public) || other.public == public)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, - uid, + id, name, - const DeepCollectionEquality().hash(_images), description, - externalUrl, + externalUri, owner, - const DeepCollectionEquality().hash(_collaborators)); + const DeepCollectionEquality().hash(_images), + const DeepCollectionEquality().hash(_collaborators), + collaborative, + public); - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$SpotubePlaylistObjectImplCopyWith<_$SpotubePlaylistObjectImpl> - get copyWith => __$$SpotubePlaylistObjectImplCopyWithImpl< - _$SpotubePlaylistObjectImpl>(this, _$identity); + _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> + get copyWith => __$$SpotubeFullPlaylistObjectImplCopyWithImpl< + _$SpotubeFullPlaylistObjectImpl>(this, _$identity); @override Map toJson() { - return _$$SpotubePlaylistObjectImplToJson( + return _$$SpotubeFullPlaylistObjectImplToJson( this, ); } } -abstract class _SpotubePlaylistObject implements SpotubePlaylistObject { - factory _SpotubePlaylistObject( - {required final String uid, - required final String name, - final List images, - required final String description, - required final String externalUrl, - required final SpotubeUserObject owner, - final List collaborators}) = - _$SpotubePlaylistObjectImpl; +abstract class _SpotubeFullPlaylistObject implements SpotubeFullPlaylistObject { + factory _SpotubeFullPlaylistObject( + {required final String id, + required final String name, + required final String description, + required final String externalUri, + required final SpotubeUserObject owner, + final List images, + final List collaborators, + final bool collaborative, + final bool public}) = _$SpotubeFullPlaylistObjectImpl; - factory _SpotubePlaylistObject.fromJson(Map json) = - _$SpotubePlaylistObjectImpl.fromJson; + factory _SpotubeFullPlaylistObject.fromJson(Map json) = + _$SpotubeFullPlaylistObjectImpl.fromJson; @override - String get uid; + String get id; @override String get name; @override - List get images; - @override String get description; @override - String get externalUrl; + String get externalUri; @override SpotubeUserObject get owner; @override + List get images; + @override List get collaborators; + @override + bool get collaborative; + @override + bool get public; - /// Create a copy of SpotubePlaylistObject + /// Create a copy of SpotubeFullPlaylistObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubePlaylistObjectImplCopyWith<_$SpotubePlaylistObjectImpl> + _$$SpotubeFullPlaylistObjectImplCopyWith<_$SpotubeFullPlaylistObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeSimplePlaylistObject _$SpotubeSimplePlaylistObjectFromJson( + Map json) { + return _SpotubeSimplePlaylistObject.fromJson(json); +} + +/// @nodoc +mixin _$SpotubeSimplePlaylistObject { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + SpotubeUserObject get owner => throw _privateConstructorUsedError; + List get images => throw _privateConstructorUsedError; + + /// Serializes this SpotubeSimplePlaylistObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $SpotubeSimplePlaylistObjectCopyWith + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $SpotubeSimplePlaylistObjectCopyWith<$Res> { + factory $SpotubeSimplePlaylistObjectCopyWith( + SpotubeSimplePlaylistObject value, + $Res Function(SpotubeSimplePlaylistObject) then) = + _$SpotubeSimplePlaylistObjectCopyWithImpl<$Res, + SpotubeSimplePlaylistObject>; + @useResult + $Res call( + {String id, + String name, + String description, + String externalUri, + SpotubeUserObject owner, + List images}); + + $SpotubeUserObjectCopyWith<$Res> get owner; +} + +/// @nodoc +class _$SpotubeSimplePlaylistObjectCopyWithImpl<$Res, + $Val extends SpotubeSimplePlaylistObject> + implements $SpotubeSimplePlaylistObjectCopyWith<$Res> { + _$SpotubeSimplePlaylistObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of SpotubeSimplePlaylistObject + /// 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? description = null, + Object? externalUri = null, + Object? owner = null, + Object? images = null, + }) { + 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, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as SpotubeUserObject, + images: null == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } + + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeUserObjectCopyWith<$Res> get owner { + return $SpotubeUserObjectCopyWith<$Res>(_value.owner, (value) { + return _then(_value.copyWith(owner: value) as $Val); + }); + } +} + +/// @nodoc +abstract class _$$SpotubeSimplePlaylistObjectImplCopyWith<$Res> + implements $SpotubeSimplePlaylistObjectCopyWith<$Res> { + factory _$$SpotubeSimplePlaylistObjectImplCopyWith( + _$SpotubeSimplePlaylistObjectImpl value, + $Res Function(_$SpotubeSimplePlaylistObjectImpl) then) = + __$$SpotubeSimplePlaylistObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String name, + String description, + String externalUri, + SpotubeUserObject owner, + List images}); + + @override + $SpotubeUserObjectCopyWith<$Res> get owner; +} + +/// @nodoc +class __$$SpotubeSimplePlaylistObjectImplCopyWithImpl<$Res> + extends _$SpotubeSimplePlaylistObjectCopyWithImpl<$Res, + _$SpotubeSimplePlaylistObjectImpl> + implements _$$SpotubeSimplePlaylistObjectImplCopyWith<$Res> { + __$$SpotubeSimplePlaylistObjectImplCopyWithImpl( + _$SpotubeSimplePlaylistObjectImpl _value, + $Res Function(_$SpotubeSimplePlaylistObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeSimplePlaylistObject + /// 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? description = null, + Object? externalUri = null, + Object? owner = null, + Object? images = null, + }) { + return _then(_$SpotubeSimplePlaylistObjectImpl( + 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, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + externalUri: null == externalUri + ? _value.externalUri + : externalUri // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as SpotubeUserObject, + images: null == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeSimplePlaylistObjectImpl + implements _SpotubeSimplePlaylistObject { + _$SpotubeSimplePlaylistObjectImpl( + {required this.id, + required this.name, + required this.description, + required this.externalUri, + required this.owner, + final List images = const []}) + : _images = images; + + factory _$SpotubeSimplePlaylistObjectImpl.fromJson( + Map json) => + _$$SpotubeSimplePlaylistObjectImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + final String description; + @override + final String externalUri; + @override + final SpotubeUserObject owner; + 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 'SpotubeSimplePlaylistObject(id: $id, name: $name, description: $description, externalUri: $externalUri, owner: $owner, images: $images)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeSimplePlaylistObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.description, description) || + other.description == description) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.owner, owner) || other.owner == owner) && + const DeepCollectionEquality().equals(other._images, _images)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, description, + externalUri, owner, const DeepCollectionEquality().hash(_images)); + + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> + get copyWith => __$$SpotubeSimplePlaylistObjectImplCopyWithImpl< + _$SpotubeSimplePlaylistObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeSimplePlaylistObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeSimplePlaylistObject + implements SpotubeSimplePlaylistObject { + factory _SpotubeSimplePlaylistObject( + {required final String id, + required final String name, + required final String description, + required final String externalUri, + required final SpotubeUserObject owner, + final List images}) = + _$SpotubeSimplePlaylistObjectImpl; + + factory _SpotubeSimplePlaylistObject.fromJson(Map json) = + _$SpotubeSimplePlaylistObjectImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + String get description; + @override + String get externalUri; + @override + SpotubeUserObject get owner; + @override + List get images; + + /// Create a copy of SpotubeSimplePlaylistObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeSimplePlaylistObjectImplCopyWith<_$SpotubeSimplePlaylistObjectImpl> get copyWith => throw _privateConstructorUsedError; } @@ -1536,17 +3198,13 @@ SpotubeSearchResponseObject _$SpotubeSearchResponseObjectFromJson( /// @nodoc mixin _$SpotubeSearchResponseObject { - @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get tracks => + List get albums => throw _privateConstructorUsedError; - @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get albums => + List get artists => throw _privateConstructorUsedError; - @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get artists => + List get playlists => throw _privateConstructorUsedError; - @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get playlists => + List get tracks => throw _privateConstructorUsedError; /// Serializes this SpotubeSearchResponseObject to a JSON map. @@ -1568,24 +3226,10 @@ abstract class $SpotubeSearchResponseObjectCopyWith<$Res> { SpotubeSearchResponseObject>; @useResult $Res call( - {@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; + {List albums, + List artists, + List playlists, + List tracks}); } /// @nodoc @@ -1604,94 +3248,30 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, @pragma('vm:prefer-inline') @override $Res call({ - Object? tracks = freezed, - Object? albums = freezed, - Object? artists = freezed, - Object? playlists = freezed, + Object? albums = null, + Object? artists = null, + Object? playlists = null, + Object? tracks = null, }) { return _then(_value.copyWith( - tracks: freezed == tracks - ? _value.tracks - : tracks // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - albums: freezed == albums + albums: null == albums ? _value.albums : albums // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - artists: freezed == artists + as List, + artists: null == artists ? _value.artists : artists // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - playlists: freezed == playlists + as List, + playlists: null == playlists ? _value.playlists : playlists // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, + as List, + tracks: null == tracks + ? _value.tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, ) 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 @@ -1704,28 +3284,10 @@ abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res> @override @useResult $Res call( - {@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; + {List albums, + List artists, + List playlists, + List tracks}); } /// @nodoc @@ -1743,28 +3305,28 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? tracks = freezed, - Object? albums = freezed, - Object? artists = freezed, - Object? playlists = freezed, + Object? albums = null, + Object? artists = null, + Object? playlists = null, + Object? tracks = null, }) { return _then(_$SpotubeSearchResponseObjectImpl( - tracks: freezed == tracks - ? _value.tracks - : tracks // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - albums: freezed == albums - ? _value.albums + albums: null == albums + ? _value._albums : albums // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - artists: freezed == artists - ? _value.artists + as List, + artists: null == artists + ? _value._artists : artists // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, - playlists: freezed == playlists - ? _value.playlists + as List, + playlists: null == playlists + ? _value._playlists : playlists // ignore: cast_nullable_to_non_nullable - as SpotubePaginationResponseObject?, + as List, + tracks: null == tracks + ? _value._tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -1774,36 +3336,54 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> class _$SpotubeSearchResponseObjectImpl implements _SpotubeSearchResponseObject { _$SpotubeSearchResponseObjectImpl( - {@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}); + {required final List albums, + required final List artists, + required final List playlists, + required final List tracks}) + : _albums = albums, + _artists = artists, + _playlists = playlists, + _tracks = tracks; factory _$SpotubeSearchResponseObjectImpl.fromJson( Map json) => _$$SpotubeSearchResponseObjectImplFromJson(json); + final List _albums; @override - @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) - final SpotubePaginationResponseObject? tracks; + List get albums { + if (_albums is EqualUnmodifiableListView) return _albums; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_albums); + } + + final List _artists; @override - @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) - final SpotubePaginationResponseObject? albums; + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + final List _playlists; @override - @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) - final SpotubePaginationResponseObject? artists; + List get playlists { + if (_playlists is EqualUnmodifiableListView) return _playlists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_playlists); + } + + final List _tracks; @override - @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) - final SpotubePaginationResponseObject? playlists; + List get tracks { + if (_tracks is EqualUnmodifiableListView) return _tracks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tracks); + } @override String toString() { - return 'SpotubeSearchResponseObject(tracks: $tracks, albums: $albums, artists: $artists, playlists: $playlists)'; + return 'SpotubeSearchResponseObject(albums: $albums, artists: $artists, playlists: $playlists, tracks: $tracks)'; } @override @@ -1811,17 +3391,21 @@ class _$SpotubeSearchResponseObjectImpl return identical(this, other) || (other.runtimeType == runtimeType && other is _$SpotubeSearchResponseObjectImpl && - (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)); + const DeepCollectionEquality().equals(other._albums, _albums) && + const DeepCollectionEquality().equals(other._artists, _artists) && + const DeepCollectionEquality() + .equals(other._playlists, _playlists) && + const DeepCollectionEquality().equals(other._tracks, _tracks)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => - Object.hash(runtimeType, tracks, albums, artists, playlists); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(_albums), + const DeepCollectionEquality().hash(_artists), + const DeepCollectionEquality().hash(_playlists), + const DeepCollectionEquality().hash(_tracks)); /// Create a copy of SpotubeSearchResponseObject /// with the given fields replaced by the non-null parameter values. @@ -1843,32 +3427,23 @@ class _$SpotubeSearchResponseObjectImpl abstract class _SpotubeSearchResponseObject implements SpotubeSearchResponseObject { factory _SpotubeSearchResponseObject( - {@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; + {required final List albums, + required final List artists, + required final List playlists, + required final List tracks}) = + _$SpotubeSearchResponseObjectImpl; factory _SpotubeSearchResponseObject.fromJson(Map json) = _$SpotubeSearchResponseObjectImpl.fromJson; @override - @JsonKey(fromJson: _paginationTracksFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get tracks; + List get albums; @override - @JsonKey(fromJson: _paginationAlbumsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get albums; + List get artists; @override - @JsonKey(fromJson: _paginationArtistsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get artists; + List get playlists; @override - @JsonKey(fromJson: _paginationPlaylistsFromJson, toJson: _paginationToJson) - SpotubePaginationResponseObject? get playlists; + List get tracks; /// Create a copy of SpotubeSearchResponseObject /// with the given fields replaced by the non-null parameter values. @@ -1878,88 +3453,98 @@ abstract class _SpotubeSearchResponseObject get copyWith => throw _privateConstructorUsedError; } -SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map json) { - return _SpotubeTrackObject.fromJson(json); +SpotubeFullTrackObject _$SpotubeFullTrackObjectFromJson( + Map json) { + return _SpotubeFullTrackObject.fromJson(json); } /// @nodoc -mixin _$SpotubeTrackObject { - String get uid => throw _privateConstructorUsedError; - String get title => throw _privateConstructorUsedError; - List get artists => throw _privateConstructorUsedError; - SpotubeAlbumObject get album => throw _privateConstructorUsedError; +mixin _$SpotubeFullTrackObject { + String get id => throw _privateConstructorUsedError; + String get name => throw _privateConstructorUsedError; + String get externalUri => throw _privateConstructorUsedError; + List get artists => + throw _privateConstructorUsedError; + SpotubeSimpleAlbumObject get album => throw _privateConstructorUsedError; int get durationMs => throw _privateConstructorUsedError; String get isrc => throw _privateConstructorUsedError; - String get externalUrl => throw _privateConstructorUsedError; + bool get explicit => throw _privateConstructorUsedError; - /// Serializes this SpotubeTrackObject to a JSON map. + /// Serializes this SpotubeFullTrackObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubeTrackObjectCopyWith get copyWith => + $SpotubeFullTrackObjectCopyWith get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubeTrackObjectCopyWith<$Res> { - factory $SpotubeTrackObjectCopyWith( - SpotubeTrackObject value, $Res Function(SpotubeTrackObject) then) = - _$SpotubeTrackObjectCopyWithImpl<$Res, SpotubeTrackObject>; +abstract class $SpotubeFullTrackObjectCopyWith<$Res> { + factory $SpotubeFullTrackObjectCopyWith(SpotubeFullTrackObject value, + $Res Function(SpotubeFullTrackObject) then) = + _$SpotubeFullTrackObjectCopyWithImpl<$Res, SpotubeFullTrackObject>; @useResult $Res call( - {String uid, - String title, - List artists, - SpotubeAlbumObject album, + {String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, int durationMs, String isrc, - String externalUrl}); + bool explicit}); - $SpotubeAlbumObjectCopyWith<$Res> get album; + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album; } /// @nodoc -class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> - implements $SpotubeTrackObjectCopyWith<$Res> { - _$SpotubeTrackObjectCopyWithImpl(this._value, this._then); +class _$SpotubeFullTrackObjectCopyWithImpl<$Res, + $Val extends SpotubeFullTrackObject> + implements $SpotubeFullTrackObjectCopyWith<$Res> { + _$SpotubeFullTrackObjectCopyWithImpl(this._value, this._then); // ignore: unused_field final $Val _value; // ignore: unused_field final $Res Function($Val) _then; - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, - Object? title = null, + Object? id = null, + Object? name = null, + Object? externalUri = null, Object? artists = null, Object? album = null, Object? durationMs = null, Object? isrc = null, - Object? externalUrl = null, + Object? explicit = null, }) { return _then(_value.copyWith( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable + 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, artists: null == artists ? _value.artists : artists // ignore: cast_nullable_to_non_nullable - as List, + as List, album: null == album ? _value.album : album // ignore: cast_nullable_to_non_nullable - as SpotubeAlbumObject, + as SpotubeSimpleAlbumObject, durationMs: null == durationMs ? _value.durationMs : durationMs // ignore: cast_nullable_to_non_nullable @@ -1968,83 +3553,92 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> ? _value.isrc : isrc // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable - as String, + explicit: null == explicit + ? _value.explicit + : explicit // ignore: cast_nullable_to_non_nullable + as bool, ) as $Val); } - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') - $SpotubeAlbumObjectCopyWith<$Res> get album { - return $SpotubeAlbumObjectCopyWith<$Res>(_value.album, (value) { + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album { + return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album, (value) { return _then(_value.copyWith(album: value) as $Val); }); } } /// @nodoc -abstract class _$$SpotubeTrackObjectImplCopyWith<$Res> - implements $SpotubeTrackObjectCopyWith<$Res> { - factory _$$SpotubeTrackObjectImplCopyWith(_$SpotubeTrackObjectImpl value, - $Res Function(_$SpotubeTrackObjectImpl) then) = - __$$SpotubeTrackObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeFullTrackObjectImplCopyWith<$Res> + implements $SpotubeFullTrackObjectCopyWith<$Res> { + factory _$$SpotubeFullTrackObjectImplCopyWith( + _$SpotubeFullTrackObjectImpl value, + $Res Function(_$SpotubeFullTrackObjectImpl) then) = + __$$SpotubeFullTrackObjectImplCopyWithImpl<$Res>; @override @useResult $Res call( - {String uid, - String title, - List artists, - SpotubeAlbumObject album, + {String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, int durationMs, String isrc, - String externalUrl}); + bool explicit}); @override - $SpotubeAlbumObjectCopyWith<$Res> get album; + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album; } /// @nodoc -class __$$SpotubeTrackObjectImplCopyWithImpl<$Res> - extends _$SpotubeTrackObjectCopyWithImpl<$Res, _$SpotubeTrackObjectImpl> - implements _$$SpotubeTrackObjectImplCopyWith<$Res> { - __$$SpotubeTrackObjectImplCopyWithImpl(_$SpotubeTrackObjectImpl _value, - $Res Function(_$SpotubeTrackObjectImpl) _then) +class __$$SpotubeFullTrackObjectImplCopyWithImpl<$Res> + extends _$SpotubeFullTrackObjectCopyWithImpl<$Res, + _$SpotubeFullTrackObjectImpl> + implements _$$SpotubeFullTrackObjectImplCopyWith<$Res> { + __$$SpotubeFullTrackObjectImplCopyWithImpl( + _$SpotubeFullTrackObjectImpl _value, + $Res Function(_$SpotubeFullTrackObjectImpl) _then) : super(_value, _then); - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ - Object? uid = null, - Object? title = null, + Object? id = null, + Object? name = null, + Object? externalUri = null, Object? artists = null, Object? album = null, Object? durationMs = null, Object? isrc = null, - Object? externalUrl = null, + Object? explicit = null, }) { - return _then(_$SpotubeTrackObjectImpl( - uid: null == uid - ? _value.uid - : uid // ignore: cast_nullable_to_non_nullable + return _then(_$SpotubeFullTrackObjectImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable + 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, artists: null == artists ? _value._artists : artists // ignore: cast_nullable_to_non_nullable - as List, + as List, album: null == album ? _value.album : album // ignore: cast_nullable_to_non_nullable - as SpotubeAlbumObject, + as SpotubeSimpleAlbumObject, durationMs: null == durationMs ? _value.durationMs : durationMs // ignore: cast_nullable_to_non_nullable @@ -2053,136 +3647,463 @@ class __$$SpotubeTrackObjectImplCopyWithImpl<$Res> ? _value.isrc : isrc // ignore: cast_nullable_to_non_nullable as String, - externalUrl: null == externalUrl - ? _value.externalUrl - : externalUrl // ignore: cast_nullable_to_non_nullable - as String, + explicit: null == explicit + ? _value.explicit + : explicit // ignore: cast_nullable_to_non_nullable + as bool, )); } } /// @nodoc @JsonSerializable() -class _$SpotubeTrackObjectImpl implements _SpotubeTrackObject { - _$SpotubeTrackObjectImpl( - {required this.uid, - required this.title, - final List artists = const [], +class _$SpotubeFullTrackObjectImpl implements _SpotubeFullTrackObject { + _$SpotubeFullTrackObjectImpl( + {required this.id, + required this.name, + required this.externalUri, + final List artists = const [], required this.album, required this.durationMs, required this.isrc, - required this.externalUrl}) + required this.explicit}) : _artists = artists; - factory _$SpotubeTrackObjectImpl.fromJson(Map json) => - _$$SpotubeTrackObjectImplFromJson(json); + factory _$SpotubeFullTrackObjectImpl.fromJson(Map json) => + _$$SpotubeFullTrackObjectImplFromJson(json); @override - final String uid; + final String id; @override - final String title; - final List _artists; + final String name; + @override + final String externalUri; + final List _artists; @override @JsonKey() - List get artists { + List get artists { if (_artists is EqualUnmodifiableListView) return _artists; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_artists); } @override - final SpotubeAlbumObject album; + final SpotubeSimpleAlbumObject album; @override final int durationMs; @override final String isrc; @override - final String externalUrl; + final bool explicit; @override String toString() { - return 'SpotubeTrackObject(uid: $uid, title: $title, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, externalUrl: $externalUrl)'; + return 'SpotubeFullTrackObject(id: $id, name: $name, externalUri: $externalUri, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, explicit: $explicit)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubeTrackObjectImpl && - (identical(other.uid, uid) || other.uid == uid) && - (identical(other.title, title) || other.title == title) && + other is _$SpotubeFullTrackObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && const DeepCollectionEquality().equals(other._artists, _artists) && (identical(other.album, album) || other.album == album) && (identical(other.durationMs, durationMs) || other.durationMs == durationMs) && (identical(other.isrc, isrc) || other.isrc == isrc) && - (identical(other.externalUrl, externalUrl) || - other.externalUrl == externalUrl)); + (identical(other.explicit, explicit) || + other.explicit == explicit)); } @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, - uid, - title, + id, + name, + externalUri, const DeepCollectionEquality().hash(_artists), album, durationMs, isrc, - externalUrl); + explicit); - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$SpotubeTrackObjectImplCopyWith<_$SpotubeTrackObjectImpl> get copyWith => - __$$SpotubeTrackObjectImplCopyWithImpl<_$SpotubeTrackObjectImpl>( - this, _$identity); + _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> + get copyWith => __$$SpotubeFullTrackObjectImplCopyWithImpl< + _$SpotubeFullTrackObjectImpl>(this, _$identity); @override Map toJson() { - return _$$SpotubeTrackObjectImplToJson( + return _$$SpotubeFullTrackObjectImplToJson( this, ); } } -abstract class _SpotubeTrackObject implements SpotubeTrackObject { - factory _SpotubeTrackObject( - {required final String uid, - required final String title, - final List artists, - required final SpotubeAlbumObject album, +abstract class _SpotubeFullTrackObject implements SpotubeFullTrackObject { + factory _SpotubeFullTrackObject( + {required final String id, + required final String name, + required final String externalUri, + final List artists, + required final SpotubeSimpleAlbumObject album, required final int durationMs, required final String isrc, - required final String externalUrl}) = _$SpotubeTrackObjectImpl; + required final bool explicit}) = _$SpotubeFullTrackObjectImpl; - factory _SpotubeTrackObject.fromJson(Map json) = - _$SpotubeTrackObjectImpl.fromJson; + factory _SpotubeFullTrackObject.fromJson(Map json) = + _$SpotubeFullTrackObjectImpl.fromJson; @override - String get uid; + String get id; @override - String get title; + String get name; @override - List get artists; + String get externalUri; @override - SpotubeAlbumObject get album; + List get artists; + @override + SpotubeSimpleAlbumObject get album; @override int get durationMs; @override String get isrc; @override - String get externalUrl; + bool get explicit; - /// Create a copy of SpotubeTrackObject + /// Create a copy of SpotubeFullTrackObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeTrackObjectImplCopyWith<_$SpotubeTrackObjectImpl> get copyWith => + _$$SpotubeFullTrackObjectImplCopyWith<_$SpotubeFullTrackObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +SpotubeSimpleTrackObject _$SpotubeSimpleTrackObjectFromJson( + Map 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 get artists => throw _privateConstructorUsedError; + SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError; + + /// Serializes this SpotubeSimpleTrackObject to a JSON map. + Map 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 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 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, + 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> { + factory _$$SpotubeSimpleTrackObjectImplCopyWith( + _$SpotubeSimpleTrackObjectImpl value, + $Res Function(_$SpotubeSimpleTrackObjectImpl) then) = + __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album}); + + @override + $SpotubeSimpleAlbumObjectCopyWith<$Res>? get album; +} + +/// @nodoc +class __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res> + extends _$SpotubeSimpleTrackObjectCopyWithImpl<$Res, + _$SpotubeSimpleTrackObjectImpl> + implements _$$SpotubeSimpleTrackObjectImplCopyWith<$Res> { + __$$SpotubeSimpleTrackObjectImplCopyWithImpl( + _$SpotubeSimpleTrackObjectImpl _value, + $Res Function(_$SpotubeSimpleTrackObjectImpl) _then) + : super(_value, _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(_$SpotubeSimpleTrackObjectImpl( + 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, + album: freezed == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as SpotubeSimpleAlbumObject?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$SpotubeSimpleTrackObjectImpl implements _SpotubeSimpleTrackObject { + _$SpotubeSimpleTrackObjectImpl( + {required this.id, + required this.name, + required this.externalUri, + required this.durationMs, + required this.explicit, + final List artists = const [], + this.album}) + : _artists = artists; + + factory _$SpotubeSimpleTrackObjectImpl.fromJson(Map json) => + _$$SpotubeSimpleTrackObjectImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + final String externalUri; + @override + final int durationMs; + @override + final bool explicit; + final List _artists; + @override + @JsonKey() + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + @override + final SpotubeSimpleAlbumObject? album; + + @override + String toString() { + return 'SpotubeSimpleTrackObject(id: $id, name: $name, externalUri: $externalUri, durationMs: $durationMs, explicit: $explicit, artists: $artists, album: $album)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeSimpleTrackObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + (identical(other.durationMs, durationMs) || + other.durationMs == durationMs) && + (identical(other.explicit, explicit) || + other.explicit == explicit) && + const DeepCollectionEquality().equals(other._artists, _artists) && + (identical(other.album, album) || other.album == album)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + name, + externalUri, + durationMs, + explicit, + const DeepCollectionEquality().hash(_artists), + album); + + /// Create a copy of SpotubeSimpleTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeSimpleTrackObjectImplCopyWith<_$SpotubeSimpleTrackObjectImpl> + get copyWith => __$$SpotubeSimpleTrackObjectImplCopyWithImpl< + _$SpotubeSimpleTrackObjectImpl>(this, _$identity); + + @override + Map toJson() { + return _$$SpotubeSimpleTrackObjectImplToJson( + this, + ); + } +} + +abstract class _SpotubeSimpleTrackObject implements SpotubeSimpleTrackObject { + factory _SpotubeSimpleTrackObject( + {required final String id, + required final String name, + required final String externalUri, + required final int durationMs, + required final bool explicit, + final List artists, + final SpotubeSimpleAlbumObject? album}) = _$SpotubeSimpleTrackObjectImpl; + + factory _SpotubeSimpleTrackObject.fromJson(Map json) = + _$SpotubeSimpleTrackObjectImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + String get externalUri; + @override + int get durationMs; + @override + bool get explicit; + @override + List get artists; + @override + SpotubeSimpleAlbumObject? get album; + + /// Create a copy of SpotubeSimpleTrackObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeSimpleTrackObjectImplCopyWith<_$SpotubeSimpleTrackObjectImpl> + get copyWith => throw _privateConstructorUsedError; } SpotubeUserObject _$SpotubeUserObjectFromJson(Map json) { diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 216cf366..5bd63591 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -6,79 +6,196 @@ part of 'metadata.dart'; // JsonSerializableGenerator // ************************************************************************** -_$SpotubeAlbumObjectImpl _$$SpotubeAlbumObjectImplFromJson(Map json) => - _$SpotubeAlbumObjectImpl( - uid: json['uid'] as String, - title: json['title'] as String, - artist: SpotubeArtistObject.fromJson( - Map.from(json['artist'] as Map)), +_$SpotubeFullAlbumObjectImpl _$$SpotubeFullAlbumObjectImplFromJson(Map json) => + _$SpotubeFullAlbumObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + artists: (json['artists'] as List) + .map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList(), images: (json['images'] as List?) ?.map((e) => SpotubeImageObject.fromJson( Map.from(e as Map))) .toList() ?? const [], releaseDate: json['releaseDate'] as String, - externalUrl: json['externalUrl'] as String, - type: $enumDecode(_$SpotubeAlbumTypeEnumMap, json['type']), + externalUri: json['externalUri'] as String, + totalTracks: (json['totalTracks'] as num).toInt(), + albumType: $enumDecode(_$SpotubeAlbumTypeEnumMap, json['albumType']), + recordLabel: json['recordLabel'] as String?, + genres: + (json['genres'] as List?)?.map((e) => e as String).toList(), ); -Map _$$SpotubeAlbumObjectImplToJson( - _$SpotubeAlbumObjectImpl instance) => +Map _$$SpotubeFullAlbumObjectImplToJson( + _$SpotubeFullAlbumObjectImpl instance) => { - 'uid': instance.uid, - 'title': instance.title, - 'artist': instance.artist.toJson(), + 'id': instance.id, + 'name': instance.name, + 'artists': instance.artists.map((e) => e.toJson()).toList(), 'images': instance.images.map((e) => e.toJson()).toList(), 'releaseDate': instance.releaseDate, - 'externalUrl': instance.externalUrl, - 'type': _$SpotubeAlbumTypeEnumMap[instance.type]!, + 'externalUri': instance.externalUri, + 'totalTracks': instance.totalTracks, + 'albumType': _$SpotubeAlbumTypeEnumMap[instance.albumType]!, + 'recordLabel': instance.recordLabel, + 'genres': instance.genres, }; const _$SpotubeAlbumTypeEnumMap = { SpotubeAlbumType.album: 'album', SpotubeAlbumType.single: 'single', + SpotubeAlbumType.compilation: 'compilation', }; -_$SpotubeArtistObjectImpl _$$SpotubeArtistObjectImplFromJson(Map json) => - _$SpotubeArtistObjectImpl( - uid: json['uid'] as String, +_$SpotubeSimpleAlbumObjectImpl _$$SpotubeSimpleAlbumObjectImplFromJson( + Map json) => + _$SpotubeSimpleAlbumObjectImpl( + id: json['id'] as String, name: json['name'] as String, + externalUri: json['externalUri'] as String, + artists: (json['artists'] as List) + .map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList(), images: (json['images'] as List?) ?.map((e) => SpotubeImageObject.fromJson( Map.from(e as Map))) .toList() ?? const [], - externalUrl: json['externalUrl'] as String, + releaseDate: json['releaseDate'] as String, + albumType: $enumDecode(_$SpotubeAlbumTypeEnumMap, json['albumType']), ); -Map _$$SpotubeArtistObjectImplToJson( - _$SpotubeArtistObjectImpl instance) => +Map _$$SpotubeSimpleAlbumObjectImplToJson( + _$SpotubeSimpleAlbumObjectImpl instance) => { - 'uid': instance.uid, + 'id': instance.id, 'name': instance.name, + 'externalUri': instance.externalUri, + 'artists': instance.artists.map((e) => e.toJson()).toList(), 'images': instance.images.map((e) => e.toJson()).toList(), - 'externalUrl': instance.externalUrl, + 'releaseDate': instance.releaseDate, + 'albumType': _$SpotubeAlbumTypeEnumMap[instance.albumType]!, }; -_$SpotubeFeedObjectImpl _$$SpotubeFeedObjectImplFromJson(Map json) => - _$SpotubeFeedObjectImpl( - uid: json['uid'] as String, +_$SpotubeFullArtistObjectImpl _$$SpotubeFullArtistObjectImplFromJson( + Map json) => + _$SpotubeFullArtistObjectImpl( + id: json['id'] as String, name: json['name'] as String, - externalUrl: json['externalUrl'] as String, + externalUri: json['externalUri'] as String, images: (json['images'] as List?) ?.map((e) => SpotubeImageObject.fromJson( Map.from(e as Map))) .toList() ?? const [], + genres: + (json['genres'] as List?)?.map((e) => e as String).toList(), + followers: (json['followers'] as num?)?.toInt(), ); -Map _$$SpotubeFeedObjectImplToJson( - _$SpotubeFeedObjectImpl instance) => +Map _$$SpotubeFullArtistObjectImplToJson( + _$SpotubeFullArtistObjectImpl instance) => { - 'uid': instance.uid, + 'id': instance.id, 'name': instance.name, - 'externalUrl': instance.externalUrl, + 'externalUri': instance.externalUri, 'images': instance.images.map((e) => e.toJson()).toList(), + 'genres': instance.genres, + 'followers': instance.followers, + }; + +_$SpotubeSimpleArtistObjectImpl _$$SpotubeSimpleArtistObjectImplFromJson( + Map json) => + _$SpotubeSimpleArtistObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + externalUri: json['externalUri'] as String, + ); + +Map _$$SpotubeSimpleArtistObjectImplToJson( + _$SpotubeSimpleArtistObjectImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'externalUri': instance.externalUri, + }; + +_$SpotubeBrowseAlbumSectionObjectImpl + _$$SpotubeBrowseAlbumSectionObjectImplFromJson(Map json) => + _$SpotubeBrowseAlbumSectionObjectImpl( + id: json['id'] as String, + title: json['title'] as String, + externalUri: json['externalUri'] as String, + itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), + items: (json['items'] as List) + .map((e) => SpotubeSimpleAlbumObject.fromJson( + Map.from(e as Map))) + .toList(), + ); + +Map _$$SpotubeBrowseAlbumSectionObjectImplToJson( + _$SpotubeBrowseAlbumSectionObjectImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'externalUri': instance.externalUri, + 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, + 'items': instance.items.map((e) => e.toJson()).toList(), + }; + +const _$SectionItemTypeEnumMap = { + SectionItemType.playlist: 'Playlist', + SectionItemType.album: 'Album', + SectionItemType.artist: 'Artist', +}; + +_$SpotubeBrowseArtistSectionObjectImpl + _$$SpotubeBrowseArtistSectionObjectImplFromJson(Map json) => + _$SpotubeBrowseArtistSectionObjectImpl( + id: json['id'] as String, + title: json['title'] as String, + externalUri: json['externalUri'] as String, + itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), + items: (json['items'] as List) + .map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList(), + ); + +Map _$$SpotubeBrowseArtistSectionObjectImplToJson( + _$SpotubeBrowseArtistSectionObjectImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'externalUri': instance.externalUri, + 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, + 'items': instance.items.map((e) => e.toJson()).toList(), + }; + +_$SpotubeBrowsePlaylistSectionObjectImpl + _$$SpotubeBrowsePlaylistSectionObjectImplFromJson(Map json) => + _$SpotubeBrowsePlaylistSectionObjectImpl( + id: json['id'] as String, + title: json['title'] as String, + externalUri: json['externalUri'] as String, + itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), + items: (json['items'] as List) + .map((e) => SpotubeSimplePlaylistObject.fromJson( + Map.from(e as Map))) + .toList(), + ); + +Map _$$SpotubeBrowsePlaylistSectionObjectImplToJson( + _$SpotubeBrowsePlaylistSectionObjectImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'externalUri': instance.externalUri, + 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, + 'items': instance.items.map((e) => e.toJson()).toList(), }; _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => @@ -102,9 +219,9 @@ _$SpotubePaginationResponseObjectImpl T Function(Object? json) fromJsonT, ) => _$SpotubePaginationResponseObjectImpl( + limit: (json['limit'] as num).toInt(), + nextOffset: (json['nextOffset'] as num?)?.toInt(), 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(), ); @@ -114,91 +231,166 @@ Map _$$SpotubePaginationResponseObjectImplToJson( Object? Function(T value) toJsonT, ) => { - 'total': instance.total, - 'nextCursor': instance.nextCursor, 'limit': instance.limit, + 'nextOffset': instance.nextOffset, + 'total': instance.total, 'hasMore': instance.hasMore, 'items': instance.items.map(toJsonT).toList(), }; -_$SpotubePlaylistObjectImpl _$$SpotubePlaylistObjectImplFromJson(Map json) => - _$SpotubePlaylistObjectImpl( - uid: json['uid'] as String, +_$SpotubeFullPlaylistObjectImpl _$$SpotubeFullPlaylistObjectImplFromJson( + Map json) => + _$SpotubeFullPlaylistObjectImpl( + id: json['id'] as String, name: json['name'] as String, + description: json['description'] as String, + externalUri: json['externalUri'] as String, + owner: SpotubeUserObject.fromJson( + Map.from(json['owner'] as Map)), images: (json['images'] as List?) ?.map((e) => SpotubeImageObject.fromJson( Map.from(e as Map))) .toList() ?? const [], - description: json['description'] as String, - externalUrl: json['externalUrl'] as String, - owner: SpotubeUserObject.fromJson( - Map.from(json['owner'] as Map)), collaborators: (json['collaborators'] as List?) ?.map((e) => SpotubeUserObject.fromJson( Map.from(e as Map))) .toList() ?? const [], + collaborative: json['collaborative'] as bool? ?? false, + public: json['public'] as bool? ?? false, ); -Map _$$SpotubePlaylistObjectImplToJson( - _$SpotubePlaylistObjectImpl instance) => +Map _$$SpotubeFullPlaylistObjectImplToJson( + _$SpotubeFullPlaylistObjectImpl instance) => { - 'uid': instance.uid, + 'id': instance.id, 'name': instance.name, - 'images': instance.images.map((e) => e.toJson()).toList(), 'description': instance.description, - 'externalUrl': instance.externalUrl, + 'externalUri': instance.externalUri, 'owner': instance.owner.toJson(), + 'images': instance.images.map((e) => e.toJson()).toList(), 'collaborators': instance.collaborators.map((e) => e.toJson()).toList(), + 'collaborative': instance.collaborative, + 'public': instance.public, + }; + +_$SpotubeSimplePlaylistObjectImpl _$$SpotubeSimplePlaylistObjectImplFromJson( + Map json) => + _$SpotubeSimplePlaylistObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + description: json['description'] as String, + externalUri: json['externalUri'] as String, + owner: SpotubeUserObject.fromJson( + Map.from(json['owner'] as Map)), + images: (json['images'] as List?) + ?.map((e) => SpotubeImageObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + ); + +Map _$$SpotubeSimplePlaylistObjectImplToJson( + _$SpotubeSimplePlaylistObjectImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'description': instance.description, + 'externalUri': instance.externalUri, + 'owner': instance.owner.toJson(), + 'images': instance.images.map((e) => e.toJson()).toList(), }; _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson( Map json) => _$SpotubeSearchResponseObjectImpl( - tracks: _paginationTracksFromJson(json['tracks'] as Map), - albums: _paginationAlbumsFromJson(json['albums'] as Map), - artists: - _paginationArtistsFromJson(json['artists'] as Map), - playlists: _paginationPlaylistsFromJson( - json['playlists'] as Map), + albums: (json['albums'] as List) + .map((e) => SpotubeSimpleAlbumObject.fromJson( + Map.from(e as Map))) + .toList(), + artists: (json['artists'] as List) + .map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList(), + playlists: (json['playlists'] as List) + .map((e) => SpotubeSimplePlaylistObject.fromJson( + Map.from(e as Map))) + .toList(), + tracks: (json['tracks'] as List) + .map((e) => SpotubeSimpleTrackObject.fromJson( + Map.from(e as Map))) + .toList(), ); Map _$$SpotubeSearchResponseObjectImplToJson( _$SpotubeSearchResponseObjectImpl instance) => { - 'tracks': _paginationToJson(instance.tracks), - 'albums': _paginationToJson(instance.albums), - 'artists': _paginationToJson(instance.artists), - 'playlists': _paginationToJson(instance.playlists), + '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': instance.tracks.map((e) => e.toJson()).toList(), }; -_$SpotubeTrackObjectImpl _$$SpotubeTrackObjectImplFromJson(Map json) => - _$SpotubeTrackObjectImpl( - uid: json['uid'] as String, - title: json['title'] as String, +_$SpotubeFullTrackObjectImpl _$$SpotubeFullTrackObjectImplFromJson(Map json) => + _$SpotubeFullTrackObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + externalUri: json['externalUri'] as String, artists: (json['artists'] as List?) - ?.map((e) => SpotubeArtistObject.fromJson( + ?.map((e) => SpotubeSimpleArtistObject.fromJson( Map.from(e as Map))) .toList() ?? const [], - album: SpotubeAlbumObject.fromJson( + album: SpotubeSimpleAlbumObject.fromJson( Map.from(json['album'] as Map)), durationMs: (json['durationMs'] as num).toInt(), isrc: json['isrc'] as String, - externalUrl: json['externalUrl'] as String, + explicit: json['explicit'] as bool, ); -Map _$$SpotubeTrackObjectImplToJson( - _$SpotubeTrackObjectImpl instance) => +Map _$$SpotubeFullTrackObjectImplToJson( + _$SpotubeFullTrackObjectImpl instance) => { - 'uid': instance.uid, - 'title': instance.title, + 'id': instance.id, + 'name': instance.name, + 'externalUri': instance.externalUri, 'artists': instance.artists.map((e) => e.toJson()).toList(), 'album': instance.album.toJson(), 'durationMs': instance.durationMs, 'isrc': instance.isrc, - 'externalUrl': instance.externalUrl, + 'explicit': instance.explicit, + }; + +_$SpotubeSimpleTrackObjectImpl _$$SpotubeSimpleTrackObjectImplFromJson( + Map json) => + _$SpotubeSimpleTrackObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + externalUri: json['externalUri'] as String, + durationMs: (json['durationMs'] as num).toInt(), + explicit: json['explicit'] as bool, + artists: (json['artists'] as List?) + ?.map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + album: json['album'] == null + ? null + : SpotubeSimpleAlbumObject.fromJson( + Map.from(json['album'] as Map)), + ); + +Map _$$SpotubeSimpleTrackObjectImplToJson( + _$SpotubeSimpleTrackObjectImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'externalUri': instance.externalUri, + 'durationMs': instance.durationMs, + 'explicit': instance.explicit, + 'artists': instance.artists.map((e) => e.toJson()).toList(), + 'album': instance.album?.toJson(), }; _$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) => diff --git a/lib/models/metadata/pagination.dart b/lib/models/metadata/pagination.dart index 6fca7128..5055a59e 100644 --- a/lib/models/metadata/pagination.dart +++ b/lib/models/metadata/pagination.dart @@ -4,9 +4,9 @@ part of 'metadata.dart'; class SpotubePaginationResponseObject with _$SpotubePaginationResponseObject { factory SpotubePaginationResponseObject({ + required int limit, + required int? nextOffset, required int total, - required String? nextCursor, - required String limit, required bool hasMore, required List items, }) = _SpotubePaginationResponseObject; diff --git a/lib/models/metadata/playlist.dart b/lib/models/metadata/playlist.dart index d6c686dd..5bb8f1ae 100644 --- a/lib/models/metadata/playlist.dart +++ b/lib/models/metadata/playlist.dart @@ -1,17 +1,34 @@ part of 'metadata.dart'; @freezed -class SpotubePlaylistObject with _$SpotubePlaylistObject { - factory SpotubePlaylistObject({ - required final String uid, - required final String name, - @Default([]) final List images, - required final String description, - required final String externalUrl, - required final SpotubeUserObject owner, - @Default([]) final List collaborators, - }) = _SpotubePlaylistObject; +class SpotubeFullPlaylistObject with _$SpotubeFullPlaylistObject { + factory SpotubeFullPlaylistObject({ + required String id, + required String name, + required String description, + required String externalUri, + required SpotubeUserObject owner, + @Default([]) List images, + @Default([]) List collaborators, + @Default(false) bool collaborative, + @Default(false) bool public, + }) = _SpotubeFullPlaylistObject; - factory SpotubePlaylistObject.fromJson(Map json) => - _$SpotubePlaylistObjectFromJson(json); + factory SpotubeFullPlaylistObject.fromJson(Map json) => + _$SpotubeFullPlaylistObjectFromJson(json); +} + +@freezed +class SpotubeSimplePlaylistObject with _$SpotubeSimplePlaylistObject { + factory SpotubeSimplePlaylistObject({ + required String id, + required String name, + required String description, + required String externalUri, + required SpotubeUserObject owner, + @Default([]) List images, + }) = _SpotubeSimplePlaylistObject; + + factory SpotubeSimplePlaylistObject.fromJson(Map json) => + _$SpotubeSimplePlaylistObjectFromJson(json); } diff --git a/lib/models/metadata/search.dart b/lib/models/metadata/search.dart index 5a379fa1..e655d892 100644 --- a/lib/models/metadata/search.dart +++ b/lib/models/metadata/search.dart @@ -1,71 +1,12 @@ 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({ - @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, + required List albums, + required List artists, + required List playlists, + required List tracks, }) = _SpotubeSearchResponseObject; factory SpotubeSearchResponseObject.fromJson(Map json) => diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index f555b27f..f56344f3 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -1,17 +1,34 @@ part of 'metadata.dart'; @freezed -class SpotubeTrackObject with _$SpotubeTrackObject { - factory SpotubeTrackObject({ - required final String uid, - required final String title, - @Default([]) final List artists, - required final SpotubeAlbumObject album, - required final int durationMs, - required final String isrc, - required final String externalUrl, - }) = _SpotubeTrackObject; +class SpotubeFullTrackObject with _$SpotubeFullTrackObject { + factory SpotubeFullTrackObject({ + required String id, + required String name, + required String externalUri, + @Default([]) List artists, + required SpotubeSimpleAlbumObject album, + required int durationMs, + required String isrc, + required bool explicit, + }) = _SpotubeFullTrackObject; - factory SpotubeTrackObject.fromJson(Map json) => - _$SpotubeTrackObjectFromJson(json); + factory SpotubeFullTrackObject.fromJson(Map json) => + _$SpotubeFullTrackObjectFromJson(json); +} + +@freezed +class SpotubeSimpleTrackObject with _$SpotubeSimpleTrackObject { + factory SpotubeSimpleTrackObject({ + required String id, + required String name, + required String externalUri, + required int durationMs, + required bool explicit, + @Default([]) List artists, + SpotubeSimpleAlbumObject? album, + }) = _SpotubeSimpleTrackObject; + + factory SpotubeSimpleTrackObject.fromJson(Map json) => + _$SpotubeSimpleTrackObjectFromJson(json); } From f8211cbcc72788306773c3e56399ebf2b7b70a37 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 20:25:38 +0600 Subject: [PATCH 12/60] feat: add user endpoint calls in metadata and paginated async notifiers --- lib/pages/settings/metadata_plugins.dart | 3 + .../metadata_plugin/library/albums.dart | 8 ++ .../metadata_plugin/library/artists.dart | 0 .../metadata_plugin/library/playlists.dart | 121 ++++++++++++++++++ .../metadata_plugin/utils/paginated.dart | 98 ++++++++++++++ lib/services/metadata/endpoints/error.dart | 12 ++ lib/services/metadata/endpoints/user.dart | 114 ++++++++++++++++- 7 files changed, 355 insertions(+), 1 deletion(-) create mode 100644 lib/provider/metadata_plugin/library/albums.dart create mode 100644 lib/provider/metadata_plugin/library/artists.dart create mode 100644 lib/provider/metadata_plugin/library/playlists.dart create mode 100644 lib/provider/metadata_plugin/utils/paginated.dart create mode 100644 lib/services/metadata/endpoints/error.dart diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index 283cf715..a6a30a7f 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -11,6 +11,7 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; @RoutePage() class SettingsMetadataProviderPage extends HookConsumerWidget { @@ -25,6 +26,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final metadataPlugin = ref.watch(metadataPluginProvider); final isAuthenticated = ref.watch(metadataPluginAuthenticatedProvider); + final user = ref.watch(metadataPluginUserProvider); + return Scaffold( headers: const [ TitleBar( diff --git a/lib/provider/metadata_plugin/library/albums.dart b/lib/provider/metadata_plugin/library/albums.dart new file mode 100644 index 00000000..32abe37b --- /dev/null +++ b/lib/provider/metadata_plugin/library/albums.dart @@ -0,0 +1,8 @@ +import 'package:riverpod/riverpod.dart'; + +class LibraryAlbumsNotifier extends AsyncNotifier { + @override + build() { + return null; + } +} diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart new file mode 100644 index 00000000..e69de29b diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart new file mode 100644 index 00000000..990764d8 --- /dev/null +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -0,0 +1,121 @@ +import 'package:collection/collection.dart'; +import 'package:flutter_riverpod/flutter_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/utils/paginated.dart'; +import 'package:spotube/services/metadata/endpoints/error.dart'; + +class FavoritePlaylistsNotifier + extends PaginatedAsyncNotifier { + FavoritePlaylistsNotifier() : super(); + + @override + fetch(int offset, int limit) async { + final playlists = await (await metadataPlugin) + ?.user + .savedPlaylists(limit: limit, offset: offset); + + return playlists!; + } + + @override + build() async { + ref.watch(metadataPluginProvider); + final playlists = await fetch(0, 20); + + return playlists; + } + + void updatePlaylist(SpotubeSimplePlaylistObject playlist) { + if (state.value == null) return; + + if (state.value!.items.none((e) => e.id == playlist.id)) return; + + state = AsyncData( + state.value!.copyWith( + items: state.value!.items + .map((element) => element.id == playlist.id ? playlist : element) + .toList() as List, + ) as SpotubePaginationResponseObject, + ); + } + + // Future addFavorite(PlaylistSimple playlist) async { + // await update((state) async { + // await spotify.invoke( + // (api) => api.playlists.followPlaylist(playlist.id!), + // ); + // return state.copyWith( + // items: [...state.items, playlist], + // ); + // }); + + // ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); + // } + + // Future removeFavorite(PlaylistSimple playlist) async { + // await update((state) async { + // await spotify.invoke( + // (api) => api.playlists.unfollowPlaylist(playlist.id!), + // ); + // return state.copyWith( + // items: state.items.where((e) => e.id != playlist.id).toList(), + // ); + // }); + + // ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); + // } + + // Future addTracks(String playlistId, List trackIds) async { + // if (state.value == null) return; + + // final spotify = ref.read(spotifyProvider); + + // await spotify.invoke( + // (api) => api.playlists.addTracks( + // trackIds.map((id) => 'spotify:track:$id').toList(), + // playlistId, + // ), + // ); + + // ref.invalidate(playlistTracksProvider(playlistId)); + // } + + // Future removeTracks(String playlistId, List trackIds) async { + // if (state.value == null) return; + + // final spotify = ref.read(spotifyProvider); + + // await spotify.invoke( + // (api) => api.playlists.removeTracks( + // trackIds.map((id) => 'spotify:track:$id').toList(), + // playlistId, + // ), + // ); + + // ref.invalidate(playlistTracksProvider(playlistId)); + // } +} + +final metadataPluginSavedPlaylistsProvider = AsyncNotifierProvider< + FavoritePlaylistsNotifier, + SpotubePaginationResponseObject>( + () => FavoritePlaylistsNotifier(), +); + +final metadataPluginIsSavedPlaylistProvider = + FutureProvider.family( + (ref, id) async { + final plugin = await ref.watch(metadataPluginProvider.future); + + if (plugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "Failed to get metadata plugin", + ); + } + + final follows = await plugin.user.isSavedPlaylist(id); + + return follows; + }, +); diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart new file mode 100644 index 00000000..3dacf751 --- /dev/null +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -0,0 +1,98 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +// ignore: implementation_imports +import 'package:riverpod/src/async_notifier.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/services/metadata/metadata.dart'; + +mixin MetadataPluginMixin +// ignore: invalid_use_of_internal_member + on AsyncNotifierBase> { + Future get metadataPlugin async => + await ref.read(metadataPluginProvider.future); +} + +// ignore: deprecated_member_use +extension on AutoDisposeAsyncNotifierProviderRef { + // When invoked keeps your provider alive for [duration] + // ignore: unused_element + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} + +// ignore: deprecated_member_use +extension on AutoDisposeRef { + // When invoked keeps your provider alive for [duration] + // ignore: unused_element + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} + +// ignore: subtype_of_sealed_class +class AsyncLoadingNext extends AsyncData { + const AsyncLoadingNext(super.value); +} + +mixin PaginatedAsyncNotifierMixin + // ignore: invalid_use_of_internal_member + on AsyncNotifierBase> { + Future> fetch(int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); + return newState.copyWith(items: [ + ...state.value!.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items as List; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final newState = await fetch( + state.nextOffset!, + state.limit, + ); + + hasMore = newState.hasMore; + return newState.copyWith(items: [ + ...state.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }); + } + + return state.value!.items as List; + } +} + +abstract class PaginatedAsyncNotifier + extends AsyncNotifier> + with PaginatedAsyncNotifierMixin, MetadataPluginMixin {} + +abstract class AutoDisposePaginatedAsyncNotifier + extends AutoDisposeAsyncNotifier> + with PaginatedAsyncNotifierMixin, MetadataPluginMixin {} diff --git a/lib/services/metadata/endpoints/error.dart b/lib/services/metadata/endpoints/error.dart new file mode 100644 index 00000000..f4c6af94 --- /dev/null +++ b/lib/services/metadata/endpoints/error.dart @@ -0,0 +1,12 @@ +class MetadataPluginException implements Exception { + final String exceptionType; + final String message; + + MetadataPluginException.noDefaultPlugin(this.message) + : exceptionType = "NoDefault"; + + @override + String toString() { + return "${exceptionType}MetadataPluginException: $message"; + } +} diff --git a/lib/services/metadata/endpoints/user.dart b/lib/services/metadata/endpoints/user.dart index ec4cd963..e85f1c88 100644 --- a/lib/services/metadata/endpoints/user.dart +++ b/lib/services/metadata/endpoints/user.dart @@ -1,15 +1,127 @@ import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; import 'package:spotube/models/metadata/metadata.dart'; class MetadataPluginUserEndpoint { final Hetu hetu; MetadataPluginUserEndpoint(this.hetu); + HTInstance get hetuMetadataUser => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("user") + as HTInstance; + Future me() async { - final raw = await hetu.eval("metadataPlugin.user.me()") as Map; + final raw = await hetuMetadataUser.invoke("me") as Map; return SpotubeUserObject.fromJson( raw.cast(), ); } + + Future> savedTracks({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataUser.invoke( + "savedTracks", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeFullTrackObject.fromJson(json.cast()), + ); + } + + Future> + savedPlaylists({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataUser.invoke( + "savedPlaylists", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeSimplePlaylistObject.fromJson(json.cast()), + ); + } + + Future> + savedAlbums({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataUser.invoke( + "savedAlbums", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeSimpleAlbumObject.fromJson(json.cast()), + ); + } + + Future> + savedArtists({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataUser.invoke( + "savedArtists", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeFullArtistObject.fromJson(json.cast()), + ); + } + + Future isSavedPlaylist(String playlistId) async { + return await hetuMetadataUser.invoke( + "isSavedPlaylist", + positionalArgs: [playlistId], + ) as bool; + } + + Future> isSavedTracks(List ids) async { + return await hetuMetadataUser.invoke( + "isSavedTracks", + positionalArgs: [ids], + ) as List; + } + + Future> isSavedAlbums(List ids) async { + return await hetuMetadataUser.invoke( + "isSavedAlbums", + positionalArgs: [ids], + ) as List; + } + + Future> isSavedArtists(List ids) async { + return await hetuMetadataUser.invoke( + "isSavedArtists", + positionalArgs: [ids], + ) as List; + } } From a9ba2582fb7527eca405c8e4d796ff63cfc03b79 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 20:53:18 +0600 Subject: [PATCH 13/60] feat: add playlist endpoint and providers --- .../metadata_plugin/library/playlists.dart | 91 ++++++------ .../metadata_plugin/tracks/playlist.dart | 36 +++++ .../metadata_plugin/utils/common.dart | 44 ++++++ .../utils/family_paginated.dart | 103 +++++++++++++ .../metadata_plugin/utils/paginated.dart | 37 +---- lib/services/metadata/endpoints/playlist.dart | 135 ++++++++++++++++++ lib/services/metadata/metadata.dart | 3 + 7 files changed, 369 insertions(+), 80 deletions(-) create mode 100644 lib/provider/metadata_plugin/tracks/playlist.dart create mode 100644 lib/provider/metadata_plugin/utils/common.dart create mode 100644 lib/provider/metadata_plugin/utils/family_paginated.dart diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index 990764d8..a017f65f 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -2,6 +2,7 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_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/tracks/playlist.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; import 'package:spotube/services/metadata/endpoints/error.dart'; @@ -40,61 +41,63 @@ class FavoritePlaylistsNotifier ); } - // Future addFavorite(PlaylistSimple playlist) async { - // await update((state) async { - // await spotify.invoke( - // (api) => api.playlists.followPlaylist(playlist.id!), - // ); - // return state.copyWith( - // items: [...state.items, playlist], - // ); - // }); + Future addFavorite(SpotubeSimplePlaylistObject playlist) async { + await update((state) async { + (await metadataPlugin)!.playlist.save(playlist.id); + return state.copyWith( + items: [...state.items, playlist], + ) as SpotubePaginationResponseObject; + }); - // ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); - // } + ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); + } - // Future removeFavorite(PlaylistSimple playlist) async { - // await update((state) async { - // await spotify.invoke( - // (api) => api.playlists.unfollowPlaylist(playlist.id!), - // ); - // return state.copyWith( - // items: state.items.where((e) => e.id != playlist.id).toList(), - // ); - // }); + Future removeFavorite(SpotubeSimplePlaylistObject playlist) async { + await update((state) async { + (await metadataPlugin)!.playlist.unsave(playlist.id); + return state.copyWith( + items: state.items + .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) + .toList() as List, + ) as SpotubePaginationResponseObject; + }); - // ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); - // } + ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); + } - // Future addTracks(String playlistId, List trackIds) async { - // if (state.value == null) return; + Future delete(SpotubeSimplePlaylistObject playlist) async { + await update((state) async { + (await metadataPlugin)!.playlist.deletePlaylist(playlist.id); + return state.copyWith( + items: state.items + .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) + .toList() as List, + ) as SpotubePaginationResponseObject; + }); - // final spotify = ref.read(spotifyProvider); + ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); + ref.invalidate(metadataPluginPlaylistTracksProvider(playlist.id)); + } - // await spotify.invoke( - // (api) => api.playlists.addTracks( - // trackIds.map((id) => 'spotify:track:$id').toList(), - // playlistId, - // ), - // ); + Future addTracks(String playlistId, List trackIds) async { + if (state.value == null) return; - // ref.invalidate(playlistTracksProvider(playlistId)); - // } + await (await metadataPlugin)! + .playlist + .addTracks(playlistId, trackIds: trackIds); - // Future removeTracks(String playlistId, List trackIds) async { - // if (state.value == null) return; + ref.invalidate(metadataPluginPlaylistTracksProvider(playlistId)); + } - // final spotify = ref.read(spotifyProvider); + Future removeTracks(String playlistId, List trackIds) async { + if (state.value == null) return; - // await spotify.invoke( - // (api) => api.playlists.removeTracks( - // trackIds.map((id) => 'spotify:track:$id').toList(), - // playlistId, - // ), - // ); + await (await metadataPlugin)! + .playlist + .removeTracks(playlistId, trackIds: trackIds); - // ref.invalidate(playlistTracksProvider(playlistId)); - // } + ref.invalidate(metadataPluginPlaylistTracksProvider(playlistId)); + } } final metadataPluginSavedPlaylistsProvider = AsyncNotifierProvider< diff --git a/lib/provider/metadata_plugin/tracks/playlist.dart b/lib/provider/metadata_plugin/tracks/playlist.dart new file mode 100644 index 00000000..c0302bb4 --- /dev/null +++ b/lib/provider/metadata_plugin/tracks/playlist.dart @@ -0,0 +1,36 @@ +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/utils/family_paginated.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; + +class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< + SpotubeFullTrackObject, String> { + PlaylistTracksNotifier() : super(); + + @override + fetch(offset, limit) async { + final tracks = await (await metadataPlugin)!.playlist.tracks( + arg, + offset: offset, + limit: limit, + ); + + return tracks; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginPlaylistTracksProvider = AutoDisposeAsyncNotifierProviderFamily< + PlaylistTracksNotifier, + SpotubePaginationResponseObject, + String>( + () => PlaylistTracksNotifier(), +); diff --git a/lib/provider/metadata_plugin/utils/common.dart b/lib/provider/metadata_plugin/utils/common.dart new file mode 100644 index 00000000..6a64bd91 --- /dev/null +++ b/lib/provider/metadata_plugin/utils/common.dart @@ -0,0 +1,44 @@ +// ignore: implementation_imports +import 'package:riverpod/src/async_notifier.dart'; +import 'dart:async'; + +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/services/metadata/metadata.dart'; + +mixin MetadataPluginMixin +// ignore: invalid_use_of_internal_member + on AsyncNotifierBase> { + Future get metadataPlugin async => + await ref.read(metadataPluginProvider.future); +} + +extension AutoDisposeAsyncNotifierCacheFor +// ignore: deprecated_member_use + on AutoDisposeAsyncNotifierProviderRef { + // When invoked keeps your provider alive for [duration] + // ignore: unused_element + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} + +// ignore: deprecated_member_use +extension AutoDisposeCacheFor on AutoDisposeRef { + // When invoked keeps your provider alive for [duration] + // ignore: unused_element + void cacheFor([Duration duration = const Duration(minutes: 5)]) { + final link = keepAlive(); + final timer = Timer(duration, () => link.close()); + onDispose(() => timer.cancel()); + } +} + +// ignore: subtype_of_sealed_class +class AsyncLoadingNext extends AsyncData { + const AsyncLoadingNext(super.value); +} diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart new file mode 100644 index 00000000..eb656431 --- /dev/null +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -0,0 +1,103 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +// ignore: implementation_imports +import 'package:riverpod/src/async_notifier.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; + +abstract class FamilyPaginatedAsyncNotifier + extends FamilyAsyncNotifier, A> + with MetadataPluginMixin { + Future> fetch(int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); + return newState.copyWith(items: [ + ...state.value!.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items as List; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final newState = await fetch( + state.nextOffset!, + state.limit, + ); + + hasMore = newState.hasMore; + return newState.copyWith(items: [ + ...state.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }); + } + + return state.value!.items as List; + } +} + +abstract class AutoDisposeFamilyPaginatedAsyncNotifier + extends AutoDisposeFamilyAsyncNotifier, + A> with MetadataPluginMixin { + Future> fetch(int offset, int limit); + + Future fetchMore() async { + if (state.value == null || !state.value!.hasMore) return; + + state = AsyncLoadingNext(state.asData!.value); + + state = await AsyncValue.guard( + () async { + final newState = await fetch( + state.value!.nextOffset!, + state.value!.limit, + ); + return newState.copyWith(items: [ + ...state.value!.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }, + ); + } + + Future> fetchAll() async { + if (state.value == null) return []; + if (!state.value!.hasMore) return state.value!.items as List; + + bool hasMore = true; + while (hasMore) { + await update((state) async { + final newState = await fetch( + state.nextOffset!, + state.limit, + ); + + hasMore = newState.hasMore; + return newState.copyWith(items: [ + ...state.items as List, + ...newState.items as List, + ]) as SpotubePaginationResponseObject; + }); + } + + return state.value!.items as List; + } +} diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index 3dacf751..c82e2f51 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -4,42 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; // ignore: implementation_imports import 'package:riverpod/src/async_notifier.dart'; -import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; -import 'package:spotube/services/metadata/metadata.dart'; - -mixin MetadataPluginMixin -// ignore: invalid_use_of_internal_member - on AsyncNotifierBase> { - Future get metadataPlugin async => - await ref.read(metadataPluginProvider.future); -} - -// ignore: deprecated_member_use -extension on AutoDisposeAsyncNotifierProviderRef { - // When invoked keeps your provider alive for [duration] - // ignore: unused_element - void cacheFor([Duration duration = const Duration(minutes: 5)]) { - final link = keepAlive(); - final timer = Timer(duration, () => link.close()); - onDispose(() => timer.cancel()); - } -} - -// ignore: deprecated_member_use -extension on AutoDisposeRef { - // When invoked keeps your provider alive for [duration] - // ignore: unused_element - void cacheFor([Duration duration = const Duration(minutes: 5)]) { - final link = keepAlive(); - final timer = Timer(duration, () => link.close()); - onDispose(() => timer.cancel()); - } -} - -// ignore: subtype_of_sealed_class -class AsyncLoadingNext extends AsyncData { - const AsyncLoadingNext(super.value); -} +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; mixin PaginatedAsyncNotifierMixin // ignore: invalid_use_of_internal_member diff --git a/lib/services/metadata/endpoints/playlist.dart b/lib/services/metadata/endpoints/playlist.dart index e69de29b..09c36446 100644 --- a/lib/services/metadata/endpoints/playlist.dart +++ b/lib/services/metadata/endpoints/playlist.dart @@ -0,0 +1,135 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginPlaylistEndpoint { + final Hetu hetu; + MetadataPluginPlaylistEndpoint(this.hetu); + + HTInstance get hetuMetadataPlaylist => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("playlist") + as HTInstance; + + Future getPlaylist(String id) async { + final raw = await hetuMetadataPlaylist + .invoke("getPlaylist", positionalArgs: [id]) as Map; + + return SpotubeFullPlaylistObject.fromJson( + raw.cast(), + ); + } + + Future> tracks( + String id, { + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataPlaylist.invoke( + "tracks", + positionalArgs: [id], + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeFullTrackObject.fromJson(json.cast()), + ); + } + + Future create( + String userId, { + required String name, + String? description, + bool? public, + bool? collaborative, + }) async { + final raw = await hetuMetadataPlaylist.invoke( + "create", + positionalArgs: [userId], + namedArgs: { + "name": name, + "description": description, + "public": public, + "collaborative": collaborative, + }..removeWhere((key, value) => value == null), + ) as Map?; + + if (raw == null) return null; + + return SpotubeFullPlaylistObject.fromJson( + raw.cast(), + ); + } + + Future update( + String playlistId, { + String? name, + String? description, + bool? public, + bool? collaborative, + }) async { + await hetuMetadataPlaylist.invoke( + "update", + positionalArgs: [playlistId], + namedArgs: { + "name": name, + "description": description, + "public": public, + "collaborative": collaborative, + }..removeWhere((key, value) => value == null), + ); + } + + Future addTracks( + String playlistId, { + required List trackIds, + int? position, + }) async { + await hetuMetadataPlaylist.invoke( + "addTracks", + positionalArgs: [playlistId], + namedArgs: { + "trackIds": trackIds, + "position": position, + }..removeWhere((key, value) => value == null), + ); + } + + Future removeTracks( + String playlistId, { + required List trackIds, + }) async { + await hetuMetadataPlaylist.invoke( + "removeTracks", + positionalArgs: [playlistId], + namedArgs: { + "trackIds": trackIds, + }..removeWhere((key, value) => value == null), + ); + } + + Future save(String playlistId) async { + await hetuMetadataPlaylist.invoke( + "save", + positionalArgs: [playlistId], + ); + } + + Future unsave(String playlistId) async { + await hetuMetadataPlaylist.invoke( + "unsave", + positionalArgs: [playlistId], + ); + } + + Future deletePlaylist(String playlistId) async { + await hetuMetadataPlaylist.invoke( + "deletePlaylist", + positionalArgs: [playlistId], + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 8f219207..01e158fe 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -12,6 +12,7 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; import 'package:spotube/services/metadata/endpoints/auth.dart'; +import 'package:spotube/services/metadata/endpoints/playlist.dart'; import 'package:spotube/services/metadata/endpoints/user.dart'; const defaultMetadataLimit = "20"; @@ -73,9 +74,11 @@ class MetadataPlugin { late final MetadataAuthEndpoint auth; late final MetadataPluginUserEndpoint user; + late final MetadataPluginPlaylistEndpoint playlist; MetadataPlugin._(this.hetu) { auth = MetadataAuthEndpoint(hetu); + playlist = MetadataPluginPlaylistEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu); } } From 326d8212f63fdd6451342d0289cedec15da59fd4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 21:07:07 +0600 Subject: [PATCH 14/60] feat: add albums metadata endpoint and provider --- .../metadata_plugin/library/albums.dart | 71 +++++++++++++++++- .../metadata_plugin/library/playlists.dart | 22 +++--- .../metadata_plugin/tracks/playlist.dart | 9 +-- .../metadata_plugin/utils/common.dart | 13 +++- lib/services/metadata/endpoints/album.dart | 75 +++++++++++++++++++ lib/services/metadata/metadata.dart | 5 +- 6 files changed, 173 insertions(+), 22 deletions(-) diff --git a/lib/provider/metadata_plugin/library/albums.dart b/lib/provider/metadata_plugin/library/albums.dart index 32abe37b..1dd61b81 100644 --- a/lib/provider/metadata_plugin/library/albums.dart +++ b/lib/provider/metadata_plugin/library/albums.dart @@ -1,8 +1,73 @@ import 'package:riverpod/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/utils/paginated.dart'; -class LibraryAlbumsNotifier extends AsyncNotifier { +class MetadataPluginSavedAlbumNotifier + extends PaginatedAsyncNotifier { @override - build() { - return null; + Future> fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin).user.savedAlbums( + limit: limit, + offset: offset, + ); + } + + @override + build() async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } + + Future addFavorite(List albums) async { + await update((state) async { + (await metadataPlugin).album.save(albums.map((e) => e.id).toList()); + return state.copyWith( + items: [...state.items, albums], + ) as SpotubePaginationResponseObject; + }); + + for (final album in albums) { + ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + } + } + + Future removeFavorite(List albums) async { + await update((state) async { + final albumIds = albums.map((e) => e.id).toList(); + (await metadataPlugin).album.unsave(albumIds); + return state.copyWith( + items: state.items + .where( + (e) => + albumIds.contains((e as SpotubeSimpleAlbumObject).id) == + false, + ) + .toList() as List, + ) as SpotubePaginationResponseObject; + }); + + for (final album in albums) { + ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + } } } + +final metadataPluginSavedAlbumsProvider = AsyncNotifierProvider< + MetadataPluginSavedAlbumNotifier, + SpotubePaginationResponseObject>( + () => MetadataPluginSavedAlbumNotifier(), +); + +final metadataPluginIsSavedAlbumProvider = + FutureProvider.autoDispose.family( + (ref, albumId) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + return metadataPlugin!.user + .isSavedAlbums([albumId]).then((value) => value.first); + }, +); diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index a017f65f..cbb5b166 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -6,17 +6,17 @@ import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; import 'package:spotube/services/metadata/endpoints/error.dart'; -class FavoritePlaylistsNotifier +class MetadataPluginSavedPlaylistsNotifier extends PaginatedAsyncNotifier { - FavoritePlaylistsNotifier() : super(); + MetadataPluginSavedPlaylistsNotifier() : super(); @override fetch(int offset, int limit) async { final playlists = await (await metadataPlugin) - ?.user + .user .savedPlaylists(limit: limit, offset: offset); - return playlists!; + return playlists; } @override @@ -43,7 +43,7 @@ class FavoritePlaylistsNotifier Future addFavorite(SpotubeSimplePlaylistObject playlist) async { await update((state) async { - (await metadataPlugin)!.playlist.save(playlist.id); + (await metadataPlugin).playlist.save(playlist.id); return state.copyWith( items: [...state.items, playlist], ) as SpotubePaginationResponseObject; @@ -54,7 +54,7 @@ class FavoritePlaylistsNotifier Future removeFavorite(SpotubeSimplePlaylistObject playlist) async { await update((state) async { - (await metadataPlugin)!.playlist.unsave(playlist.id); + (await metadataPlugin).playlist.unsave(playlist.id); return state.copyWith( items: state.items .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) @@ -67,7 +67,7 @@ class FavoritePlaylistsNotifier Future delete(SpotubeSimplePlaylistObject playlist) async { await update((state) async { - (await metadataPlugin)!.playlist.deletePlaylist(playlist.id); + (await metadataPlugin).playlist.deletePlaylist(playlist.id); return state.copyWith( items: state.items .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) @@ -82,7 +82,7 @@ class FavoritePlaylistsNotifier Future addTracks(String playlistId, List trackIds) async { if (state.value == null) return; - await (await metadataPlugin)! + await (await metadataPlugin) .playlist .addTracks(playlistId, trackIds: trackIds); @@ -92,7 +92,7 @@ class FavoritePlaylistsNotifier Future removeTracks(String playlistId, List trackIds) async { if (state.value == null) return; - await (await metadataPlugin)! + await (await metadataPlugin) .playlist .removeTracks(playlistId, trackIds: trackIds); @@ -101,9 +101,9 @@ class FavoritePlaylistsNotifier } final metadataPluginSavedPlaylistsProvider = AsyncNotifierProvider< - FavoritePlaylistsNotifier, + MetadataPluginSavedPlaylistsNotifier, SpotubePaginationResponseObject>( - () => FavoritePlaylistsNotifier(), + () => MetadataPluginSavedPlaylistsNotifier(), ); final metadataPluginIsSavedPlaylistProvider = diff --git a/lib/provider/metadata_plugin/tracks/playlist.dart b/lib/provider/metadata_plugin/tracks/playlist.dart index c0302bb4..42bae41c 100644 --- a/lib/provider/metadata_plugin/tracks/playlist.dart +++ b/lib/provider/metadata_plugin/tracks/playlist.dart @@ -10,7 +10,7 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< @override fetch(offset, limit) async { - final tracks = await (await metadataPlugin)!.playlist.tracks( + final tracks = await (await metadataPlugin).playlist.tracks( arg, offset: offset, limit: limit, @@ -28,9 +28,8 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< } } -final metadataPluginPlaylistTracksProvider = AutoDisposeAsyncNotifierProviderFamily< - PlaylistTracksNotifier, - SpotubePaginationResponseObject, - String>( +final metadataPluginPlaylistTracksProvider = + AutoDisposeAsyncNotifierProviderFamily, String>( () => PlaylistTracksNotifier(), ); diff --git a/lib/provider/metadata_plugin/utils/common.dart b/lib/provider/metadata_plugin/utils/common.dart index 6a64bd91..2c9877a3 100644 --- a/lib/provider/metadata_plugin/utils/common.dart +++ b/lib/provider/metadata_plugin/utils/common.dart @@ -6,13 +6,22 @@ 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/services/metadata/endpoints/error.dart'; import 'package:spotube/services/metadata/metadata.dart'; mixin MetadataPluginMixin // ignore: invalid_use_of_internal_member on AsyncNotifierBase> { - Future get metadataPlugin async => - await ref.read(metadataPluginProvider.future); + Future get metadataPlugin async { + final plugin = await ref.read(metadataPluginProvider.future); + + if (plugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "Metadata plugin is not set"); + } + + return plugin; + } } extension AutoDisposeAsyncNotifierCacheFor diff --git a/lib/services/metadata/endpoints/album.dart b/lib/services/metadata/endpoints/album.dart index e69de29b..cac324d6 100644 --- a/lib/services/metadata/endpoints/album.dart +++ b/lib/services/metadata/endpoints/album.dart @@ -0,0 +1,75 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginAlbumEndpoint { + final Hetu hetu; + MetadataPluginAlbumEndpoint(this.hetu); + + HTInstance get hetuMetadataAlbum => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("album") + as HTInstance; + + Future getAlbum(String id) async { + final raw = + await hetuMetadataAlbum.invoke("getAlbum", positionalArgs: [id]) as Map; + + return SpotubeFullAlbumObject.fromJson( + raw.cast(), + ); + } + + Future> tracks( + String id, { + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataAlbum.invoke( + "tracks", + positionalArgs: [id], + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeFullTrackObject.fromJson(json.cast()), + ); + } + + Future> releases({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataAlbum.invoke( + "releases", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => + SpotubeSimpleAlbumObject.fromJson(json.cast()), + ); + } + + Future save(List ids) async { + await hetuMetadataAlbum.invoke( + "save", + positionalArgs: [ids], + ); + } + + Future unsave(List ids) async { + await hetuMetadataAlbum.invoke( + "unsave", + positionalArgs: [ids], + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 01e158fe..fb63403e 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -11,6 +11,7 @@ import 'package:spotube/collections/routes.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; +import 'package:spotube/services/metadata/endpoints/album.dart'; import 'package:spotube/services/metadata/endpoints/auth.dart'; import 'package:spotube/services/metadata/endpoints/playlist.dart'; import 'package:spotube/services/metadata/endpoints/user.dart'; @@ -73,11 +74,13 @@ class MetadataPlugin { final Hetu hetu; late final MetadataAuthEndpoint auth; - late final MetadataPluginUserEndpoint user; + late final MetadataPluginAlbumEndpoint album; late final MetadataPluginPlaylistEndpoint playlist; + late final MetadataPluginUserEndpoint user; MetadataPlugin._(this.hetu) { auth = MetadataAuthEndpoint(hetu); + album = MetadataPluginAlbumEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu); } From 758b0bc9d936aa47b62f35b1d4fa1e37198fa5a9 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 14 Jun 2025 22:43:20 +0600 Subject: [PATCH 15/60] feat: add artist and album providers --- lib/provider/metadata_plugin/album/album.dart | 22 ++++++ .../metadata_plugin/artist/albums.dart | 32 ++++++++ .../metadata_plugin/artist/artist.dart | 22 ++++++ .../metadata_plugin/artist/top_tracks.dart | 38 +++++++++ .../metadata_plugin/library/artists.dart | 73 +++++++++++++++++ .../metadata_plugin/playlist/playlist.dart | 22 ++++++ .../metadata_plugin/tracks/album.dart | 36 +++++++++ .../metadata_plugin/tracks/playlist.dart | 11 +-- lib/services/metadata/endpoints/artist.dart | 79 +++++++++++++++++++ lib/services/metadata/metadata.dart | 5 ++ 10 files changed, 335 insertions(+), 5 deletions(-) create mode 100644 lib/provider/metadata_plugin/album/album.dart create mode 100644 lib/provider/metadata_plugin/artist/albums.dart create mode 100644 lib/provider/metadata_plugin/artist/artist.dart create mode 100644 lib/provider/metadata_plugin/artist/top_tracks.dart create mode 100644 lib/provider/metadata_plugin/playlist/playlist.dart create mode 100644 lib/provider/metadata_plugin/tracks/album.dart diff --git a/lib/provider/metadata_plugin/album/album.dart b/lib/provider/metadata_plugin/album/album.dart new file mode 100644 index 00000000..72c62202 --- /dev/null +++ b/lib/provider/metadata_plugin/album/album.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_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/utils/common.dart'; +import 'package:spotube/services/metadata/endpoints/error.dart'; + +final metadataPluginAlbumProvider = + FutureProvider.autoDispose.family( + (ref, id) async { + ref.cacheFor(); + + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No metadata plugin is not set", + ); + } + + return metadataPlugin.album.getAlbum(id); + }, +); diff --git a/lib/provider/metadata_plugin/artist/albums.dart b/lib/provider/metadata_plugin/artist/albums.dart new file mode 100644 index 00000000..c9fd87b9 --- /dev/null +++ b/lib/provider/metadata_plugin/artist/albums.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_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/utils/family_paginated.dart'; + +class MetadataPluginArtistAlbumNotifier + extends FamilyPaginatedAsyncNotifier { + @override + Future> fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin).artist.albums( + arg, + limit: limit, + offset: offset, + ); + } + + @override + build(arg) async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginArtistAlbumsProvider = AsyncNotifierFamilyProvider< + MetadataPluginArtistAlbumNotifier, + SpotubePaginationResponseObject, + String>( + () => MetadataPluginArtistAlbumNotifier(), +); diff --git a/lib/provider/metadata_plugin/artist/artist.dart b/lib/provider/metadata_plugin/artist/artist.dart new file mode 100644 index 00000000..e55d6103 --- /dev/null +++ b/lib/provider/metadata_plugin/artist/artist.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_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/utils/common.dart'; +import 'package:spotube/services/metadata/endpoints/error.dart'; + +final metadataPluginArtistProvider = + FutureProvider.autoDispose.family( + (ref, artistId) async { + ref.cacheFor(); + + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No metadata plugin is not set", + ); + } + + return metadataPlugin.artist.getArtist(artistId); + }, +); diff --git a/lib/provider/metadata_plugin/artist/top_tracks.dart b/lib/provider/metadata_plugin/artist/top_tracks.dart new file mode 100644 index 00000000..c622a738 --- /dev/null +++ b/lib/provider/metadata_plugin/artist/top_tracks.dart @@ -0,0 +1,38 @@ +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/utils/family_paginated.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; + +class MetadataPluginArtistTopTracksNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginArtistTopTracksNotifier() : super(); + + @override + fetch(offset, limit) async { + final tracks = await (await metadataPlugin).artist.topTracks( + arg, + offset: offset, + limit: limit, + ); + + return tracks; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginArtistTopTracksProvider = + AutoDisposeAsyncNotifierProviderFamily< + MetadataPluginArtistTopTracksNotifier, + SpotubePaginationResponseObject, + String>( + () => MetadataPluginArtistTopTracksNotifier(), +); diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart index e69de29b..5b8de747 100644 --- a/lib/provider/metadata_plugin/library/artists.dart +++ b/lib/provider/metadata_plugin/library/artists.dart @@ -0,0 +1,73 @@ +import 'package:riverpod/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/utils/paginated.dart'; + +class MetadataPluginSavedArtistNotifier + extends PaginatedAsyncNotifier { + @override + Future> fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin).user.savedArtists( + limit: limit, + offset: offset, + ); + } + + @override + build() async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } + + Future addFavorite(List artists) async { + await update((state) async { + (await metadataPlugin).artist.save(artists.map((e) => e.id).toList()); + return state.copyWith( + items: [...state.items, artists], + ) as SpotubePaginationResponseObject; + }); + + for (final artist in artists) { + ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id)); + } + } + + Future removeFavorite(List artists) async { + await update((state) async { + final artistIds = artists.map((e) => e.id).toList(); + (await metadataPlugin).artist.unsave(artistIds); + return state.copyWith( + items: state.items + .where( + (e) => + artistIds.contains((e as SpotubeFullArtistObject).id) == + false, + ) + .toList() as List, + ) as SpotubePaginationResponseObject; + }); + + for (final artist in artists) { + ref.invalidate(metadataPluginIsSavedArtistProvider(artist.id)); + } + } +} + +final metadataPluginSavedArtistsProvider = AsyncNotifierProvider< + MetadataPluginSavedArtistNotifier, + SpotubePaginationResponseObject>( + () => MetadataPluginSavedArtistNotifier(), +); + +final metadataPluginIsSavedArtistProvider = + FutureProvider.autoDispose.family( + (ref, artistId) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + return metadataPlugin!.user + .isSavedArtists([artistId]).then((value) => value.first); + }, +); diff --git a/lib/provider/metadata_plugin/playlist/playlist.dart b/lib/provider/metadata_plugin/playlist/playlist.dart new file mode 100644 index 00000000..9752cb21 --- /dev/null +++ b/lib/provider/metadata_plugin/playlist/playlist.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_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/utils/common.dart'; +import 'package:spotube/services/metadata/endpoints/error.dart'; + +final metadataPluginPlaylistProvider = + FutureProvider.autoDispose.family( + (ref, id) async { + ref.cacheFor(); + + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No metadata plugin is not set", + ); + } + + return metadataPlugin.playlist.getPlaylist(id); + }, +); diff --git a/lib/provider/metadata_plugin/tracks/album.dart b/lib/provider/metadata_plugin/tracks/album.dart new file mode 100644 index 00000000..5491bdd0 --- /dev/null +++ b/lib/provider/metadata_plugin/tracks/album.dart @@ -0,0 +1,36 @@ +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/utils/family_paginated.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; + +class MetadataPluginAlbumTracksNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginAlbumTracksNotifier() : super(); + + @override + fetch(offset, limit) async { + final tracks = await (await metadataPlugin).album.tracks( + arg, + offset: offset, + limit: limit, + ); + + return tracks; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginAlbumTracksProvider = + AutoDisposeAsyncNotifierProviderFamily, String>( + () => MetadataPluginAlbumTracksNotifier(), +); diff --git a/lib/provider/metadata_plugin/tracks/playlist.dart b/lib/provider/metadata_plugin/tracks/playlist.dart index 42bae41c..7fdd47db 100644 --- a/lib/provider/metadata_plugin/tracks/playlist.dart +++ b/lib/provider/metadata_plugin/tracks/playlist.dart @@ -4,9 +4,10 @@ import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; -class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< - SpotubeFullTrackObject, String> { - PlaylistTracksNotifier() : super(); +class MetadataPluginPlaylistTracksNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginPlaylistTracksNotifier() : super(); @override fetch(offset, limit) async { @@ -29,7 +30,7 @@ class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< } final metadataPluginPlaylistTracksProvider = - AutoDisposeAsyncNotifierProviderFamily, String>( - () => PlaylistTracksNotifier(), + () => MetadataPluginPlaylistTracksNotifier(), ); diff --git a/lib/services/metadata/endpoints/artist.dart b/lib/services/metadata/endpoints/artist.dart index e69de29b..f8ff22f8 100644 --- a/lib/services/metadata/endpoints/artist.dart +++ b/lib/services/metadata/endpoints/artist.dart @@ -0,0 +1,79 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginArtistEndpoint { + final Hetu hetu; + MetadataPluginArtistEndpoint(this.hetu); + + HTInstance get hetuMetadataArtist => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("artist") + as HTInstance; + + Future getArtist(String id) async { + final raw = await hetuMetadataArtist + .invoke("getArtist", positionalArgs: [id]) as Map; + + return SpotubeFullArtistObject.fromJson( + raw.cast(), + ); + } + + Future> topTracks( + String id, { + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataArtist.invoke( + "topTracks", + positionalArgs: [id], + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => SpotubeFullTrackObject.fromJson( + json.cast(), + ), + ); + } + + Future> albums( + String id, { + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataArtist.invoke( + "albums", + positionalArgs: [id], + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (Map json) => SpotubeSimpleAlbumObject.fromJson( + json.cast(), + ), + ); + } + + Future save(List ids) async { + await hetuMetadataArtist.invoke( + "save", + positionalArgs: [ids], + ); + } + + Future unsave(List ids) async { + await hetuMetadataArtist.invoke( + "unsave", + positionalArgs: [ids], + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index fb63403e..38d35d23 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -12,6 +12,7 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; import 'package:spotube/services/metadata/endpoints/album.dart'; +import 'package:spotube/services/metadata/endpoints/artist.dart'; import 'package:spotube/services/metadata/endpoints/auth.dart'; import 'package:spotube/services/metadata/endpoints/playlist.dart'; import 'package:spotube/services/metadata/endpoints/user.dart'; @@ -74,12 +75,16 @@ class MetadataPlugin { final Hetu hetu; late final MetadataAuthEndpoint auth; + late final MetadataPluginAlbumEndpoint album; + late final MetadataPluginArtistEndpoint artist; late final MetadataPluginPlaylistEndpoint playlist; late final MetadataPluginUserEndpoint user; MetadataPlugin._(this.hetu) { auth = MetadataAuthEndpoint(hetu); + + artist = MetadataPluginArtistEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu); From 4b09f6c96b35fb7eb120b3a2f338585953810508 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 15 Jun 2025 14:00:34 +0600 Subject: [PATCH 16/60] refactor: use metadata album & playlist object for card and pages --- lib/collections/fake.dart | 31 +++--- lib/collections/routes.gr.dart | 47 ++++---- .../dialogs/track_details_dialog.dart | 12 +-- .../horizontal_playbutton_card_view.dart | 6 +- lib/components/track_tile/track_options.dart | 6 +- lib/components/track_tile/track_tile.dart | 8 +- lib/hooks/configurators/use_deep_linking.dart | 22 ++-- lib/models/metadata/artist.dart | 12 +++ lib/models/metadata/image.dart | 30 ++++++ lib/models/metadata/metadata.dart | 3 + lib/models/metadata/pagination.dart | 6 +- lib/modules/album/album_card.dart | 33 +++--- .../home/sections/friends/friend_item.dart | 6 +- .../genres/genre_card_playlist_card.dart | 6 +- lib/modules/playlist/playlist_card.dart | 100 +++++++++--------- lib/modules/stats/common/album_item.dart | 18 ++-- lib/modules/stats/common/playlist_item.dart | 2 +- lib/pages/album/album.dart | 44 ++++---- lib/pages/home/feed/feed_section.dart | 86 +++++++-------- lib/pages/home/genres/genre_playlists.dart | 38 +++---- .../playlist_generate_result.dart | 16 +-- lib/pages/library/user_albums.dart | 13 ++- lib/pages/library/user_playlists.dart | 61 ++++++----- lib/pages/playlist/liked_playlist.dart | 7 +- lib/pages/playlist/playlist.dart | 39 ++++--- lib/pages/track/track.dart | 20 ++-- .../metadata_plugin/library/albums.dart | 12 +-- .../metadata_plugin/library/artists.dart | 12 +-- .../metadata_plugin/library/playlists.dart | 18 ++-- .../utils/family_paginated.dart | 37 +++---- .../metadata_plugin/utils/paginated.dart | 23 ++-- lib/services/metadata/endpoints/album.dart | 4 +- lib/services/metadata/endpoints/artist.dart | 4 +- lib/services/metadata/endpoints/playlist.dart | 2 +- lib/services/metadata/endpoints/user.dart | 9 +- pubspec.yaml | 2 +- 36 files changed, 427 insertions(+), 368 deletions(-) diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart index 8af40e71..840c75f9 100644 --- a/lib/collections/fake.dart +++ b/lib/collections/fake.dart @@ -1,5 +1,6 @@ import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/spotify/home_feed.dart'; import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/provider/history/summary.dart'; @@ -64,24 +65,26 @@ abstract class FakeData { ..uri = "uri" ..externalUrls = externalUrls; - static final AlbumSimple albumSimple = AlbumSimple() - ..id = "1" - ..albumType = AlbumType.album - ..artists = [artistSimple] - ..availableMarkets = [Market.BD] - ..externalUrls = externalUrls - ..href = "text" - ..images = [image] - ..name = "A good album" - ..releaseDate = "2021-01-01" - ..releaseDatePrecision = DatePrecision.day - ..type = "type" - ..uri = "uri"; + static final SpotubeSimpleAlbumObject albumSimple = SpotubeSimpleAlbumObject( + albumType: SpotubeAlbumType.album, + artists: [], + externalUri: "https://example.com", + id: "1", + name: "A good album", + releaseDate: "2021-01-01", + images: [ + SpotubeImageObject( + height: 1, + width: 1, + url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg", + ) + ], + ); static final Track track = Track() ..id = "1" ..artists = [artist, artist, artist] - ..album = albumSimple + // ..album = albumSimple ..availableMarkets = [Market.BD] ..discNumber = 1 ..durationMs = 50000 diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index 291254d7..798c0ad4 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -10,9 +10,10 @@ // ignore_for_file: no_leading_underscores_for_library_prefixes import 'package:auto_route/auto_route.dart' as _i44; import 'package:flutter/material.dart' as _i45; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i47; -import 'package:spotify/spotify.dart' as _i46; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i48; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48; +import 'package:spotify/spotify.dart' as _i47; +import 'package:spotube/models/metadata/metadata.dart' as _i46; +import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i49; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -86,7 +87,7 @@ class AlbumRoute extends _i44.PageRouteInfo { AlbumRoute({ _i45.Key? key, required String id, - required _i46.AlbumSimple album, + required _i46.SpotubeSimpleAlbumObject album, List<_i44.PageRouteInfo>? children, }) : super( AlbumRoute.name, @@ -125,7 +126,7 @@ class AlbumRouteArgs { final String id; - final _i46.AlbumSimple album; + final _i46.SpotubeSimpleAlbumObject album; @override String toString() { @@ -264,7 +265,7 @@ class GenrePlaylistsRoute extends _i44.PageRouteInfo { GenrePlaylistsRoute({ _i45.Key? key, required String id, - required _i46.Category category, + required _i47.Category category, List<_i44.PageRouteInfo>? children, }) : super( GenrePlaylistsRoute.name, @@ -303,7 +304,7 @@ class GenrePlaylistsRouteArgs { final String id; - final _i46.Category category; + final _i47.Category category; @override String toString() { @@ -335,7 +336,7 @@ class GettingStartedRoute extends _i44.PageRouteInfo { class HomeFeedSectionRoute extends _i44.PageRouteInfo { HomeFeedSectionRoute({ - _i47.Key? key, + _i48.Key? key, required String sectionUri, List<_i44.PageRouteInfo>? children, }) : super( @@ -371,7 +372,7 @@ class HomeFeedSectionRouteArgs { required this.sectionUri, }); - final _i47.Key? key; + final _i48.Key? key; final String sectionUri; @@ -443,7 +444,7 @@ class LibraryRoute extends _i44.PageRouteInfo { class LikedPlaylistRoute extends _i44.PageRouteInfo { LikedPlaylistRoute({ _i45.Key? key, - required _i46.PlaylistSimple playlist, + required _i46.SpotubeSimplePlaylistObject playlist, List<_i44.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, @@ -476,7 +477,7 @@ class LikedPlaylistRouteArgs { final _i45.Key? key; - final _i46.PlaylistSimple playlist; + final _i46.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -584,8 +585,8 @@ class LyricsRoute extends _i44.PageRouteInfo { /// [_i18.MiniLyricsPage] class MiniLyricsRoute extends _i44.PageRouteInfo { MiniLyricsRoute({ - _i47.Key? key, - required _i47.Size prevSize, + _i48.Key? key, + required _i48.Size prevSize, List<_i44.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, @@ -616,9 +617,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i47.Key? key; + final _i48.Key? key; - final _i47.Size prevSize; + final _i48.Size prevSize; @override String toString() { @@ -688,8 +689,8 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { class PlaylistGenerateResultRoute extends _i44.PageRouteInfo { PlaylistGenerateResultRoute({ - _i47.Key? key, - required _i48.GeneratePlaylistProviderInput state, + _i48.Key? key, + required _i49.GeneratePlaylistProviderInput state, List<_i44.PageRouteInfo>? children, }) : super( PlaylistGenerateResultRoute.name, @@ -720,9 +721,9 @@ class PlaylistGenerateResultRouteArgs { required this.state, }); - final _i47.Key? key; + final _i48.Key? key; - final _i48.GeneratePlaylistProviderInput state; + final _i49.GeneratePlaylistProviderInput state; @override String toString() { @@ -755,7 +756,7 @@ class PlaylistRoute extends _i44.PageRouteInfo { PlaylistRoute({ _i45.Key? key, required String id, - required _i46.PlaylistSimple playlist, + required _i46.SpotubeSimplePlaylistObject playlist, List<_i44.PageRouteInfo>? children, }) : super( PlaylistRoute.name, @@ -794,7 +795,7 @@ class PlaylistRouteArgs { final String id; - final _i46.PlaylistSimple playlist; + final _i46.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -1034,7 +1035,7 @@ class StatsStreamsRoute extends _i44.PageRouteInfo { /// [_i37.TrackPage] class TrackRoute extends _i44.PageRouteInfo { TrackRoute({ - _i47.Key? key, + _i48.Key? key, required String trackId, List<_i44.PageRouteInfo>? children, }) : super( @@ -1069,7 +1070,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i47.Key? key; + final _i48.Key? key; final String trackId; diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index bfb4a318..7237afc6 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -31,12 +31,12 @@ class TrackDetailsDialog extends HookWidget { textStyle: const TextStyle(color: Colors.blue), hideOverflowArtist: false, ), - context.l10n.album: LinkText( - track.album!.name!, - AlbumRoute(album: track.album!, id: track.album!.id!), - overflow: TextOverflow.ellipsis, - style: const TextStyle(color: Colors.blue), - ), + // context.l10n.album: LinkText( + // track.album!.name!, + // AlbumRoute(album: track.album!, id: track.album!.id!), + // overflow: TextOverflow.ellipsis, + // style: const TextStyle(color: Colors.blue), + // ), context.l10n.duration: (track is SourcedTrack ? (track as SourcedTrack).sourceInfo.duration : track.duration!) diff --git a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart index 47fb0f33..385c2603 100644 --- a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart +++ b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart @@ -6,6 +6,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/modules/artist/artist_card.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; @@ -99,8 +100,9 @@ class HorizontalPlaybuttonCardView extends HookWidget { return switch (item) { PlaylistSimple() => - PlaylistCard(item as PlaylistSimple), - AlbumSimple() => AlbumCard(item as AlbumSimple), + PlaylistCard(item as SpotubeSimplePlaylistObject), + AlbumSimple() => + AlbumCard(item as SpotubeSimpleAlbumObject), Artist() => ArtistCard(item as Artist), _ => const SizedBox.shrink(), }; diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index a0738165..0136c419 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -215,9 +215,9 @@ class TrackOptions extends HookConsumerWidget { onSelected: (value) async { switch (value) { case TrackOptionValue.album: - await context.navigateTo( - AlbumRoute(id: track.album!.id!, album: track.album!), - ); + // await context.navigateTo( + // AlbumRoute(id: track.album!.id!, album: track.album!), + // ); break; case TrackOptionValue.delete: await File((track as LocalTrack).path).delete(); diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index f47980cd..9d9045c5 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -258,13 +258,15 @@ class TrackTile extends HookConsumerWidget { ), _ => Align( alignment: Alignment.centerLeft, - child: LinkText( + /* child: LinkText( track.album!.name!, AlbumRoute( - album: track.album!, id: track.album!.id!), + album: track.album!, + id: track.album!.id!, + ), push: true, overflow: TextOverflow.ellipsis, - ), + ), */ ) }, ), diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index a141a21d..fa23091e 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -30,9 +30,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final album = await spotify.invoke((api) { return api.albums.get(url.pathSegments.last); }); - router.navigate( - AlbumRoute(id: album.id!, album: album), - ); + // router.navigate( + // AlbumRoute(id: album.id!, album: album), + // ); break; case "artist": router.navigate(ArtistRoute(artistId: url.pathSegments.last)); @@ -41,8 +41,8 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final playlist = await spotify.invoke((api) { return api.playlists.get(url.pathSegments.last); }); - router - .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); + // router + // .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); break; case "track": router.navigate(TrackRoute(trackId: url.pathSegments.last)); @@ -72,9 +72,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final album = await spotify.invoke((api) { return api.albums.get(endSegment); }); - await router.navigate( - AlbumRoute(id: album.id!, album: album), - ); + // await router.navigate( + // AlbumRoute(id: album.id!, album: album), + // ); break; case "spotify:artist": await router.navigate(ArtistRoute(artistId: endSegment)); @@ -86,9 +86,9 @@ void useDeepLinking(WidgetRef ref, AppRouter router) { final playlist = await spotify.invoke((api) { return api.playlists.get(endSegment); }); - await router.navigate( - PlaylistRoute(id: playlist.id!, playlist: playlist), - ); + // await router.navigate( + // PlaylistRoute(id: playlist.id!, playlist: playlist), + // ); break; default: break; diff --git a/lib/models/metadata/artist.dart b/lib/models/metadata/artist.dart index 2bee1191..89c59082 100644 --- a/lib/models/metadata/artist.dart +++ b/lib/models/metadata/artist.dart @@ -26,3 +26,15 @@ class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject { factory SpotubeSimpleArtistObject.fromJson(Map json) => _$SpotubeSimpleArtistObjectFromJson(json); } + +extension SpotubeFullArtistObjectAsString on List { + String asString() { + return map((e) => e.name).join(", "); + } +} + +extension SpotubeSimpleArtistObjectAsString on List { + String asString() { + return map((e) => e.name).join(", "); + } +} diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index 1fc3bb30..c058b120 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -11,3 +11,33 @@ class SpotubeImageObject with _$SpotubeImageObject { factory SpotubeImageObject.fromJson(Map json) => _$SpotubeImageObjectFromJson(json); } + +enum ImagePlaceholder { + albumArt, + artist, + collection, + online, +} + +extension SpotifyImageExtensions on List? { + String asUrlString({ + int index = 1, + required ImagePlaceholder placeholder, + }) { + final String placeholderUrl = { + ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, + ImagePlaceholder.artist: Assets.userPlaceholder.path, + ImagePlaceholder.collection: Assets.placeholder.path, + ImagePlaceholder.online: + "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", + }[placeholder]!; + + final sortedImage = this?.sorted((a, b) => a.width!.compareTo(b.width!)); + + return sortedImage != null && sortedImage.isNotEmpty + ? sortedImage[ + index > sortedImage.length - 1 ? sortedImage.length - 1 : index] + .url + : placeholderUrl; + } +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 6e70ef99..c3ce0436 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -1,6 +1,9 @@ library metadata_objects; +import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/utils/primitive_utils.dart'; part 'metadata.g.dart'; part 'metadata.freezed.dart'; diff --git a/lib/models/metadata/pagination.dart b/lib/models/metadata/pagination.dart index 5055a59e..093c1d2b 100644 --- a/lib/models/metadata/pagination.dart +++ b/lib/models/metadata/pagination.dart @@ -2,20 +2,20 @@ part of 'metadata.dart'; @Freezed(genericArgumentFactories: true) class SpotubePaginationResponseObject - with _$SpotubePaginationResponseObject { + with _$SpotubePaginationResponseObject { factory SpotubePaginationResponseObject({ required int limit, required int? nextOffset, required int total, required bool hasMore, required List items, - }) = _SpotubePaginationResponseObject; + }) = _SpotubePaginationResponseObject; factory SpotubePaginationResponseObject.fromJson( Map json, T Function(Map json) fromJsonT, ) => - _$SpotubePaginationResponseObjectFromJson( + _$SpotubePaginationResponseObjectFromJson( json, (json) => fromJsonT(json as Map), ); diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index 5fee9cc4..33a7dc01 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -9,13 +9,14 @@ import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -24,7 +25,7 @@ extension FormattedAlbumType on AlbumType { } class AlbumCard extends HookConsumerWidget { - final AlbumSimple album; + final SpotubeSimpleAlbumObject album; final bool _isTile; const AlbumCard( this.album, { @@ -46,18 +47,15 @@ class AlbumCard extends HookConsumerWidget { final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); bool isPlaylistPlaying = useMemoized( - () => playlist.containsCollection(album.id!), + () => playlist.containsCollection(album.id), [playlist, album.id], ); final updating = useState(false); Future> fetchAllTrack() async { - if (album.tracks != null && album.tracks!.isNotEmpty) { - return album.tracks!.asTracks(album, ref); - } - await ref.read(albumTracksProvider(album).future); - return ref.read(albumTracksProvider(album).notifier).fetchAll(); + // return ref.read(metadataPluginAlbumTracksProvider(album).notifier).fetchAll(); + return []; } var imageUrl = album.images.asUrlString( @@ -65,11 +63,10 @@ class AlbumCard extends HookConsumerWidget { ); var isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - var description = - "${album.albumType?.formatted} • ${album.artists?.asString() ?? ""}"; + var description = "${album.albumType} • ${album.artists.asString()}"; void onTap() { - context.navigateTo(AlbumRoute(id: album.id!, album: album)); + context.navigateTo(AlbumRoute(id: album.id, album: album)); } void onPlaybuttonPressed() async { @@ -90,13 +87,13 @@ class AlbumCard extends HookConsumerWidget { await remotePlayback.load( WebSocketLoadEventData.album( tracks: fetchedTracks, - collection: album, + // collection: album, ), ); } else { await playlistNotifier.load(fetchedTracks, autoPlay: true); - playlistNotifier.addCollection(album.id!); - historyNotifier.addAlbums([album]); + playlistNotifier.addCollection(album.id); + // historyNotifier.addAlbums([album]); } } finally { updating.value = false; @@ -114,8 +111,8 @@ class AlbumCard extends HookConsumerWidget { if (fetchedTracks.isEmpty) return; playlistNotifier.addTracks(fetchedTracks); - playlistNotifier.addCollection(album.id!); - historyNotifier.addAlbums([album]); + playlistNotifier.addCollection(album.id); + // historyNotifier.addAlbums([album]); if (context.mounted) { showToast( context: context, @@ -147,7 +144,7 @@ class AlbumCard extends HookConsumerWidget { imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, - title: album.name!, + title: album.name, description: description, onTap: onTap, onPlaybuttonPressed: onPlaybuttonPressed, @@ -159,7 +156,7 @@ class AlbumCard extends HookConsumerWidget { imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, - title: album.name!, + title: album.name, description: description, onTap: onTap, onPlaybuttonPressed: onPlaybuttonPressed, diff --git a/lib/modules/home/sections/friends/friend_item.dart b/lib/modules/home/sections/friends/friend_item.dart index bf04558f..00617404 100644 --- a/lib/modules/home/sections/friends/friend_item.dart +++ b/lib/modules/home/sections/friends/friend_item.dart @@ -99,9 +99,9 @@ class FriendItem extends HookConsumerWidget { (api) => api.albums.get(friend.track.album.id), ); if (context.mounted) { - context.navigateTo( - AlbumRoute(id: album.id!, album: album), - ); + // context.navigateTo( + // AlbumRoute(id: album.id!, album: album), + // ); } }, ), diff --git a/lib/modules/home/sections/genres/genre_card_playlist_card.dart b/lib/modules/home/sections/genres/genre_card_playlist_card.dart index 328507cc..7bae4503 100644 --- a/lib/modules/home/sections/genres/genre_card_playlist_card.dart +++ b/lib/modules/home/sections/genres/genre_card_playlist_card.dart @@ -47,9 +47,9 @@ class GenreSectionCardPlaylistCard extends HookConsumerWidget { }, ), onPressed: () { - context.navigateTo( - PlaylistRoute(id: playlist.id!, playlist: playlist), - ); + // context.navigateTo( + // PlaylistRoute(id: playlist.id!, playlist: playlist), + // ); }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index c4ffffa7..3d180e7c 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -9,8 +9,8 @@ import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; @@ -20,7 +20,7 @@ import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:stroke_text/stroke_text.dart'; class PlaylistCard extends HookConsumerWidget { - final PlaylistSimple playlist; + final SpotubeSimplePlaylistObject playlist; final bool _isTile; const PlaylistCard( @@ -43,7 +43,7 @@ class PlaylistCard extends HookConsumerWidget { final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; bool isPlaylistPlaying = useMemoized( - () => playlistQueue.containsCollection(playlist.id!), + () => playlistQueue.containsCollection(playlist.id), [playlistQueue, playlist.id], ); @@ -55,8 +55,7 @@ class PlaylistCard extends HookConsumerWidget { return await ref.read(likedTracksProvider.future); } - final result = - await ref.read(playlistTracksProvider(playlist.id!).future); + final result = await ref.read(playlistTracksProvider(playlist.id).future); return result.items; } @@ -68,11 +67,11 @@ class PlaylistCard extends HookConsumerWidget { return initialTracks; } - return ref.read(playlistTracksProvider(playlist.id!).notifier).fetchAll(); + return ref.read(playlistTracksProvider(playlist.id).notifier).fetchAll(); } void onTap() { - context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); + context.navigateTo(PlaylistRoute(id: playlist.id, playlist: playlist)); } void onPlaybuttonPressed() async { @@ -96,13 +95,13 @@ class PlaylistCard extends HookConsumerWidget { await remotePlayback.load( WebSocketLoadEventData.playlist( tracks: allTracks, - collection: playlist, + // collection: playlist, ), ); } else { await playlistNotifier.load(fetchedInitialTracks, autoPlay: true); - playlistNotifier.addCollection(playlist.id!); - historyNotifier.addPlaylists([playlist]); + playlistNotifier.addCollection(playlist.id); + // historyNotifier.addPlaylists([playlist]); final allTracks = await fetchAllTracks(); @@ -126,8 +125,8 @@ class PlaylistCard extends HookConsumerWidget { if (fetchedInitialTracks.isEmpty) return; playlistNotifier.addTracks(fetchedInitialTracks); - playlistNotifier.addCollection(playlist.id!); - historyNotifier.addPlaylists([playlist]); + playlistNotifier.addCollection(playlist.id); + // historyNotifier.addPlaylists([playlist]); if (context.mounted) { showToast( context: context, @@ -160,50 +159,49 @@ class PlaylistCard extends HookConsumerWidget { ); final isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - final isOwner = playlist.owner?.id == me.asData?.value.id && - me.asData?.value.id != null; + final isOwner = + playlist.owner.id == me.asData?.value.id && me.asData?.value.id != null; - final image = - playlist.owner?.displayName == "Spotify" && Env.disableSpotifyImages - ? Consumer( - builder: (context, ref, child) { - final (:color, :colorBlendMode, :src, :placement) = - ref.watch(playlistImageProvider(playlist.id!)); + final image = playlist.owner.name == "Spotify" && Env.disableSpotifyImages + ? Consumer( + builder: (context, ref, child) { + final (:color, :colorBlendMode, :src, :placement) = + ref.watch(playlistImageProvider(playlist.id)); - return Stack( - children: [ - Positioned.fill( - child: Image.asset( - src, - color: color, - colorBlendMode: colorBlendMode, - fit: BoxFit.cover, - ), + return Stack( + children: [ + Positioned.fill( + child: Image.asset( + src, + color: color, + colorBlendMode: colorBlendMode, + fit: BoxFit.cover, + ), + ), + Positioned.fill( + top: placement == Alignment.topLeft ? 10 : null, + left: 10, + bottom: placement == Alignment.bottomLeft ? 10 : null, + child: StrokeText( + text: playlist.name, + strokeColor: Colors.white, + strokeWidth: 3, + textColor: Colors.black, + textStyle: const TextStyle( + fontSize: 16, + fontStyle: FontStyle.italic, ), - Positioned.fill( - top: placement == Alignment.topLeft ? 10 : null, - left: 10, - bottom: placement == Alignment.bottomLeft ? 10 : null, - child: StrokeText( - text: playlist.name!, - strokeColor: Colors.white, - strokeWidth: 3, - textColor: Colors.black, - textStyle: const TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ); - }, - ) - : null; + ), + ), + ], + ); + }, + ) + : null; if (_isTile) { return PlaybuttonTile( - title: playlist.name!, + title: playlist.name, description: playlist.description, image: image, imageUrl: image == null ? imageUrl : null, @@ -217,7 +215,7 @@ class PlaylistCard extends HookConsumerWidget { } return PlaybuttonCard( - title: playlist.name!, + title: playlist.name, description: playlist.description, image: image, imageUrl: image == null ? imageUrl : null, diff --git a/lib/modules/stats/common/album_item.dart b/lib/modules/stats/common/album_item.dart index cd0a6caf..b69e1d15 100644 --- a/lib/modules/stats/common/album_item.dart +++ b/lib/modules/stats/common/album_item.dart @@ -32,19 +32,19 @@ class StatsAlbumItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text("${album.albumType?.formatted} • "), - Flexible( - child: ArtistLink( - artists: album.artists ?? [], - mainAxisAlignment: WrapAlignment.start, - onOverflowArtistClick: () => - context.navigateTo(AlbumRoute(id: album.id!, album: album)), - ), - ), + // Flexible( + // child: ArtistLink( + // artists: album.artists ?? [], + // mainAxisAlignment: WrapAlignment.start, + // onOverflowArtistClick: () => + // context.navigateTo(AlbumRoute(id: album.id!, album: album)), + // ), + // ), ], ), trailing: info, onPressed: () { - context.navigateTo(AlbumRoute(id: album.id!, album: album)); + // context.navigateTo(AlbumRoute(id: album.id!, album: album)); }, ); } diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index 58610af1..b1fdc920 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -35,7 +35,7 @@ class StatsPlaylistItem extends StatelessWidget { ), trailing: info, onPressed: () { - context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); + // context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); }, ); } diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 5773f9fa..dd57b2e3 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -2,18 +2,19 @@ import 'package:flutter/material.dart' as material; import 'package:auto_route/auto_route.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/albums.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; import 'package:spotube/provider/spotify/spotify.dart'; @RoutePage() class AlbumPage extends HookConsumerWidget { static const name = "album"; - final AlbumSimple album; + final SpotubeSimpleAlbumObject album; final String id; const AlbumPage({ super.key, @@ -23,16 +24,19 @@ class AlbumPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final tracks = ref.watch(albumTracksProvider(album)); - final tracksNotifier = ref.watch(albumTracksProvider(album).notifier); - final favoriteAlbumsNotifier = ref.watch(favoriteAlbumsProvider.notifier); - final isSavedAlbum = ref.watch(albumsIsSavedProvider(album.id!)); + final tracks = ref.watch(metadataPluginAlbumTracksProvider(album.id)); + final tracksNotifier = + ref.watch(metadataPluginAlbumTracksProvider(album.id).notifier); + final favoriteAlbumsNotifier = + ref.watch(metadataPluginSavedAlbumsProvider.notifier); + final isSavedAlbum = + ref.watch(metadataPluginIsSavedAlbumProvider(album.id)); return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(albumTracksProvider(album)); - ref.invalidate(favoriteAlbumsProvider); - ref.invalidate(albumsIsSavedProvider(album.id!)); + ref.invalidate(metadataPluginAlbumTracksProvider(album.id)); + ref.invalidate(metadataPluginIsSavedAlbumProvider(album.id)); + ref.invalidate(metadataPluginSavedAlbumsProvider); }, child: TrackPresentation( options: TrackPresentationOptions( @@ -40,10 +44,10 @@ class AlbumPage extends HookConsumerWidget { image: album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), - title: album.name!, + title: album.name, description: - "${context.l10n.released} • ${album.releaseDate} • ${album.artists!.first.name}", - tracks: tracks.asData?.value.items ?? [], + "${context.l10n.released} • ${album.releaseDate} • ${album.artists.first.name}", + tracks: [], pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, isLoading: tracks.isLoading || tracks.isLoadingNextPage, @@ -51,24 +55,24 @@ class AlbumPage extends HookConsumerWidget { await tracksNotifier.fetchMore(); }, onFetchAll: () async { - return tracksNotifier.fetchAll(); + // return tracksNotifier.fetchAll(); + return []; }, onRefresh: () async { - ref.invalidate(albumTracksProvider(album)); + // ref.invalidate(albumTracksProvider(album)); }, ), routePath: "/album/${album.id}", - shareUrl: album.externalUrls?.spotify ?? - "https://open.spotify.com/album/${album.id}", + shareUrl: album.externalUri, isLiked: isSavedAlbum.asData?.value ?? false, - owner: album.artists!.first.name, + owner: album.artists.first.name, onHeart: isSavedAlbum.asData?.value == null ? null : () async { if (isSavedAlbum.asData!.value) { - await favoriteAlbumsNotifier.removeFavorites([album.id!]); + await favoriteAlbumsNotifier.removeFavorite([album]); } else { - await favoriteAlbumsNotifier.addFavorites([album.id!]); + await favoriteAlbumsNotifier.addFavorite([album]); } return null; }, diff --git a/lib/pages/home/feed/feed_section.dart b/lib/pages/home/feed/feed_section.dart index 2b38d0ed..5ece363d 100644 --- a/lib/pages/home/feed/feed_section.dart +++ b/lib/pages/home/feed/feed_section.dart @@ -43,49 +43,49 @@ class HomeFeedSectionPage extends HookConsumerWidget { child: CustomScrollView( controller: controller, slivers: [ - if (isArtist) - SliverGrid.builder( - gridDelegate: - const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: 250, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemCount: section.items.length, - itemBuilder: (context, index) { - final item = section.items[index]; - return ArtistCard(item.artist!.asArtist); - }, - ) - else - PlaybuttonView( - controller: controller, - itemCount: section.items.length, - hasMore: false, - isLoading: false, - onRequestMore: () => {}, - listItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard.tile(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard.tile(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, - gridItemBuilder: (context, index) { - final item = section.items[index]; - if (item.album != null) { - return AlbumCard(item.album!.asAlbum); - } - if (item.playlist != null) { - return PlaylistCard(item.playlist!.asPlaylist); - } - return const SizedBox.shrink(); - }, - ), + // if (isArtist) + // SliverGrid.builder( + // gridDelegate: + // const SliverGridDelegateWithMaxCrossAxisExtent( + // maxCrossAxisExtent: 200, + // mainAxisExtent: 250, + // crossAxisSpacing: 8, + // mainAxisSpacing: 8, + // ), + // itemCount: section.items.length, + // itemBuilder: (context, index) { + // final item = section.items[index]; + // return ArtistCard(item.artist!.asArtist); + // }, + // ) + // else + // PlaybuttonView( + // controller: controller, + // itemCount: section.items.length, + // hasMore: false, + // isLoading: false, + // onRequestMore: () => {}, + // listItemBuilder: (context, index) { + // final item = section.items[index]; + // if (item.album != null) { + // return AlbumCard.tile(item.album!.asAlbum); + // } + // if (item.playlist != null) { + // return PlaylistCard.tile(item.playlist!.asPlaylist); + // } + // return const SizedBox.shrink(); + // }, + // gridItemBuilder: (context, index) { + // final item = section.items[index]; + // if (item.album != null) { + // return AlbumCard(item.album!.asAlbum); + // } + // if (item.playlist != null) { + // return PlaylistCard(item.playlist!.asPlaylist); + // } + // return const SizedBox.shrink(); + // }, + // ), const SliverToBoxAdapter( child: SafeArea( child: SizedBox(), diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart index ea421cb4..bf36f834 100644 --- a/lib/pages/home/genres/genre_playlists.dart +++ b/lib/pages/home/genres/genre_playlists.dart @@ -115,25 +115,25 @@ class GenrePlaylistsPage extends HookConsumerWidget { ), ), const SliverGap(20), - SliverSafeArea( - top: false, - sliver: SliverPadding( - padding: EdgeInsets.symmetric( - horizontal: mediaQuery.mdAndDown ? 12 : 24, - ), - sliver: PlaybuttonView( - controller: scrollController, - itemCount: playlists.asData?.value.items.length ?? 0, - isLoading: playlists.isLoading, - hasMore: playlists.asData?.value.hasMore == true, - onRequestMore: playlistsNotifier.fetchMore, - listItemBuilder: (context, index) => PlaylistCard.tile( - playlists.asData!.value.items[index]), - gridItemBuilder: (context, index) => - PlaylistCard(playlists.asData!.value.items[index]), - ), - ), - ), + // SliverSafeArea( + // top: false, + // sliver: SliverPadding( + // padding: EdgeInsets.symmetric( + // horizontal: mediaQuery.mdAndDown ? 12 : 24, + // ), + // sliver: PlaybuttonView( + // controller: scrollController, + // itemCount: playlists.asData?.value.items.length ?? 0, + // isLoading: playlists.isLoading, + // hasMore: playlists.asData?.value.hasMore == true, + // onRequestMore: playlistsNotifier.fetchMore, + // listItemBuilder: (context, index) => PlaylistCard.tile( + // playlists.asData!.value.items[index]), + // gridItemBuilder: (context, index) => + // PlaylistCard(playlists.asData!.value.items[index]), + // ), + // ), + // ), const SliverGap(20), ], ), diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart index 9e6f2987..87ae9fe4 100644 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ b/lib/pages/library/playlist_generate/playlist_generate_result.dart @@ -137,14 +137,14 @@ class PlaylistGenerateResultPage extends HookConsumerWidget { ), ); - if (playlist != null && context.mounted) { - context.navigateTo( - PlaylistRoute( - id: playlist.id!, - playlist: playlist, - ), - ); - } + // if (playlist != null && context.mounted) { + // context.navigateTo( + // PlaylistRoute( + // id: playlist.id!, + // playlist: playlist, + // ), + // ); + // } }, child: Text(context.l10n.create_a_playlist), ), diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index beaa779f..4534e531 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -15,6 +15,8 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -25,9 +27,10 @@ class UserAlbumsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); - final albumsQuery = ref.watch(favoriteAlbumsProvider); - final albumsQueryNotifier = ref.watch(favoriteAlbumsProvider.notifier); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); + final albumsQuery = ref.watch(metadataPluginSavedAlbumsProvider); + final albumsQueryNotifier = + ref.watch(metadataPluginSavedAlbumsProvider.notifier); final controller = useScrollController(); @@ -39,7 +42,7 @@ class UserAlbumsPage extends HookConsumerWidget { } return albumsQuery.asData?.value.items .map((e) => ( - weightedRatio(e.name!, searchText.value), + weightedRatio(e.name, searchText.value), e, )) .sorted((a, b) => b.$1.compareTo(a.$1)) @@ -49,7 +52,7 @@ class UserAlbumsPage extends HookConsumerWidget { []; }, [albumsQuery.asData?.value, searchText.value]); - if (auth.asData?.value == null) { + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index 8b9e0dc3..d12c54b9 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -5,18 +5,20 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -28,42 +30,47 @@ class UserPlaylistsPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final searchText = useState(''); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); - final playlistsQuery = ref.watch(favoritePlaylistsProvider); + final me = ref.watch(metadataPluginUserProvider); + final playlistsQuery = ref.watch(metadataPluginSavedPlaylistsProvider); final playlistsQueryNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); final likedTracksPlaylist = useMemoized( - () => PlaylistSimple() - ..name = context.l10n.liked_tracks - ..description = context.l10n.liked_tracks_description - ..type = "playlist" - ..collaborative = false - ..public = false - ..id = "user-liked-tracks" - ..images = [ - Image() - ..height = 300 - ..width = 300 - ..url = "assets/liked-tracks.jpg" - ], - [context.l10n], + () => me.asData?.value == null + ? null + : SpotubeSimplePlaylistObject( + id: "liked-tracks", + name: context.l10n.liked_tracks, + description: context.l10n.liked_tracks_description, + externalUri: "", + owner: me.asData!.value!, + images: [ + SpotubeImageObject( + url: Assets.likedTracks.path, + width: 300, + height: 300, + ) + ]), + [context.l10n, me.asData?.value], ); final playlists = useMemoized( () { if (searchText.value.isEmpty) { return [ - likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items, + if (likedTracksPlaylist != null) likedTracksPlaylist, + ...?playlistsQuery.asData?.value.items + as List?, ]; } return [ - likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items, + if (likedTracksPlaylist != null) likedTracksPlaylist, + ...?playlistsQuery.asData?.value.items + as List?, ] - .map((e) => (weightedRatio(e.name!, searchText.value), e)) + .map((e) => (weightedRatio(e.name, searchText.value), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) .where((e) => e.$1 > 50) .map((e) => e.$2) @@ -74,13 +81,13 @@ class UserPlaylistsPage extends HookConsumerWidget { final controller = useScrollController(); - if (auth.asData?.value == null) { + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(favoritePlaylistsProvider); + ref.invalidate(metadataPluginSavedPlaylistsProvider); }, child: SafeArea( bottom: false, diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 5f7591ab..5575b9d8 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -4,6 +4,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotify/spotify.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/playlist/playlist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -12,7 +13,7 @@ import 'package:auto_route/auto_route.dart'; class LikedPlaylistPage extends HookConsumerWidget { static const name = PlaylistPage.name; - final PlaylistSimple playlist; + final SpotubeSimplePlaylistObject playlist; const LikedPlaylistPage({ super.key, required this.playlist, @@ -42,14 +43,14 @@ class LikedPlaylistPage extends HookConsumerWidget { ref.invalidate(likedTracksProvider); }, ), - title: playlist.name!, + title: playlist.name, description: playlist.description, tracks: tracks, routePath: '/playlist/${playlist.id}', isLiked: false, shareUrl: null, onHeart: null, - owner: playlist.owner?.displayName, + owner: playlist.owner.name, ), ), ); diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index 62ced353..94c07ac1 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart' as material; import 'package:collection/collection.dart'; import 'package:flutter/material.dart' hide Page; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/components/track_presentation/use_is_user_playlist.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -16,22 +16,22 @@ import 'package:auto_route/auto_route.dart'; class PlaylistPage extends HookConsumerWidget { static const name = "playlist"; - final PlaylistSimple _playlist; + final SpotubeSimplePlaylistObject _playlist; final String id; const PlaylistPage({ super.key, @PathParam("id") required this.id, - required PlaylistSimple playlist, + required SpotubeSimplePlaylistObject playlist, }) : _playlist = playlist; @override Widget build(BuildContext context, ref) { final playlist = ref .watch( - favoritePlaylistsProvider.select( + metadataPluginSavedPlaylistsProvider.select( (value) => value.whenData( - (value) => - value.items.firstWhereOrNull((s) => s.id == _playlist.id), + (value) => (value.items as List) + .firstWhereOrNull((s) => s.id == _playlist.id), ), ), ) @@ -39,21 +39,21 @@ class PlaylistPage extends HookConsumerWidget { ?.value ?? _playlist; - final tracks = ref.watch(playlistTracksProvider(playlist.id!)); + final tracks = ref.watch(playlistTracksProvider(playlist.id)); final tracksNotifier = - ref.watch(playlistTracksProvider(playlist.id!).notifier); + ref.watch(playlistTracksProvider(playlist.id).notifier); final isFavoritePlaylist = - ref.watch(isFavoritePlaylistProvider(playlist.id!)); + ref.watch(isFavoritePlaylistProvider(playlist.id)); final favoritePlaylistsNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); - final isUserPlaylist = useIsUserPlaylist(ref, playlist.id!); + final isUserPlaylist = useIsUserPlaylist(ref, playlist.id); return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id!)); - ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); + ref.invalidate(playlistTracksProvider(playlist.id)); + ref.invalidate(isFavoritePlaylistProvider(playlist.id)); ref.invalidate(favoritePlaylistsProvider); }, child: TrackPresentation( @@ -67,21 +67,20 @@ class PlaylistPage extends HookConsumerWidget { isLoading: tracks.isLoading || tracks.isLoadingNextPage, onFetchMore: tracksNotifier.fetchMore, onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id!)); + ref.invalidate(playlistTracksProvider(playlist.id)); }, onFetchAll: () async { return await tracksNotifier.fetchAll(); }, ), - title: playlist.name!, + title: playlist.name, description: playlist.description, - owner: playlist.owner?.displayName, - ownerImage: playlist.owner?.images?.lastOrNull?.url, + owner: playlist.owner.name, + ownerImage: playlist.owner.images.lastOrNull?.url, tracks: tracks.asData?.value.items ?? [], routePath: '/playlist/${playlist.id}', isLiked: isFavoritePlaylist.asData?.value ?? false, - shareUrl: playlist.externalUrls?.spotify ?? - "https://open.spotify.com/playlist/${playlist.id}", + shareUrl: playlist.externalUri, onHeart: isFavoritePlaylist.asData?.value == null ? null : () async { diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 8e6df748..9f94650b 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -142,16 +142,16 @@ class TrackPage extends HookConsumerWidget { children: [ const Icon(SpotubeIcons.album), const Gap(5), - Flexible( - child: LinkText( - track.album!.name!, - AlbumRoute( - id: track.album!.id!, - album: track.album!, - ), - push: true, - ), - ), + // Flexible( + // child: LinkText( + // track.album!.name!, + // AlbumRoute( + // id: track.album!.id!, + // album: track.album!, + // ), + // push: true, + // ), + // ), ], ), const Gap(10), diff --git a/lib/provider/metadata_plugin/library/albums.dart b/lib/provider/metadata_plugin/library/albums.dart index 1dd61b81..daa21151 100644 --- a/lib/provider/metadata_plugin/library/albums.dart +++ b/lib/provider/metadata_plugin/library/albums.dart @@ -26,8 +26,8 @@ class MetadataPluginSavedAlbumNotifier await update((state) async { (await metadataPlugin).album.save(albums.map((e) => e.id).toList()); return state.copyWith( - items: [...state.items, albums], - ) as SpotubePaginationResponseObject; + items: [...state.items, ...albums], + ); }); for (final album in albums) { @@ -42,12 +42,10 @@ class MetadataPluginSavedAlbumNotifier return state.copyWith( items: state.items .where( - (e) => - albumIds.contains((e as SpotubeSimpleAlbumObject).id) == - false, + (e) => albumIds.contains((e).id) == false, ) - .toList() as List, - ) as SpotubePaginationResponseObject; + .toList(), + ); }); for (final album in albums) { diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart index 5b8de747..5d489e00 100644 --- a/lib/provider/metadata_plugin/library/artists.dart +++ b/lib/provider/metadata_plugin/library/artists.dart @@ -26,8 +26,8 @@ class MetadataPluginSavedArtistNotifier await update((state) async { (await metadataPlugin).artist.save(artists.map((e) => e.id).toList()); return state.copyWith( - items: [...state.items, artists], - ) as SpotubePaginationResponseObject; + items: [...state.items, ...artists], + ); }); for (final artist in artists) { @@ -42,12 +42,10 @@ class MetadataPluginSavedArtistNotifier return state.copyWith( items: state.items .where( - (e) => - artistIds.contains((e as SpotubeFullArtistObject).id) == - false, + (e) => artistIds.contains((e).id) == false, ) - .toList() as List, - ) as SpotubePaginationResponseObject; + .toList(), + ); }); for (final artist in artists) { diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index cbb5b166..1ebe2a63 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -36,8 +36,8 @@ class MetadataPluginSavedPlaylistsNotifier state.value!.copyWith( items: state.value!.items .map((element) => element.id == playlist.id ? playlist : element) - .toList() as List, - ) as SpotubePaginationResponseObject, + .toList(), + ), ); } @@ -46,7 +46,7 @@ class MetadataPluginSavedPlaylistsNotifier (await metadataPlugin).playlist.save(playlist.id); return state.copyWith( items: [...state.items, playlist], - ) as SpotubePaginationResponseObject; + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); @@ -56,10 +56,8 @@ class MetadataPluginSavedPlaylistsNotifier await update((state) async { (await metadataPlugin).playlist.unsave(playlist.id); return state.copyWith( - items: state.items - .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) - .toList() as List, - ) as SpotubePaginationResponseObject; + items: state.items.where((e) => (e).id != playlist.id).toList(), + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); @@ -69,10 +67,8 @@ class MetadataPluginSavedPlaylistsNotifier await update((state) async { (await metadataPlugin).playlist.deletePlaylist(playlist.id); return state.copyWith( - items: state.items - .where((e) => (e as SpotubeSimplePlaylistObject).id != playlist.id) - .toList() as List, - ) as SpotubePaginationResponseObject; + items: state.items.where((e) => (e).id != playlist.id).toList(), + ); }); ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart index eb656431..a994bdab 100644 --- a/lib/provider/metadata_plugin/utils/family_paginated.dart +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -2,8 +2,6 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -// ignore: implementation_imports -import 'package:riverpod/src/async_notifier.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; abstract class FamilyPaginatedAsyncNotifier @@ -22,17 +20,20 @@ abstract class FamilyPaginatedAsyncNotifier state.value!.nextOffset!, state.value!.limit, ); - return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }, ); } Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -43,14 +44,14 @@ abstract class FamilyPaginatedAsyncNotifier ); hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + final oldItems = state.items.isEmpty ? [] : state.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } @@ -71,8 +72,8 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier state.value!.limit, ); return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, + ...state.value!.items.cast(), + ...newState.items.cast(), ]) as SpotubePaginationResponseObject; }, ); @@ -80,7 +81,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -92,12 +93,12 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier hasMore = newState.hasMore; return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, + ...state.items.cast(), + ...newState.items.cast(), ]) as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index c82e2f51..6a37929d 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -22,17 +22,20 @@ mixin PaginatedAsyncNotifierMixin state.value!.nextOffset!, state.value!.limit, ); - return newState.copyWith(items: [ - ...state.value!.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + + final oldItems = + state.value!.items.isEmpty ? [] : state.value!.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }, ); } Future> fetchAll() async { if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items as List; + if (!state.value!.hasMore) return state.value!.items.cast(); bool hasMore = true; while (hasMore) { @@ -43,14 +46,14 @@ mixin PaginatedAsyncNotifierMixin ); hasMore = newState.hasMore; - return newState.copyWith(items: [ - ...state.items as List, - ...newState.items as List, - ]) as SpotubePaginationResponseObject; + final oldItems = state.items.isEmpty ? [] : state.items.cast(); + final items = newState.items.isEmpty ? [] : newState.items.cast(); + return newState.copyWith(items: [...oldItems, ...items]) + as SpotubePaginationResponseObject; }); } - return state.value!.items as List; + return state.value!.items.cast(); } } diff --git a/lib/services/metadata/endpoints/album.dart b/lib/services/metadata/endpoints/album.dart index cac324d6..8a858343 100644 --- a/lib/services/metadata/endpoints/album.dart +++ b/lib/services/metadata/endpoints/album.dart @@ -33,7 +33,7 @@ class MetadataPluginAlbumEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), @@ -52,7 +52,7 @@ class MetadataPluginAlbumEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson(json.cast()), diff --git a/lib/services/metadata/endpoints/artist.dart b/lib/services/metadata/endpoints/artist.dart index f8ff22f8..188e9ea3 100644 --- a/lib/services/metadata/endpoints/artist.dart +++ b/lib/services/metadata/endpoints/artist.dart @@ -33,7 +33,7 @@ class MetadataPluginArtistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson( json.cast(), @@ -55,7 +55,7 @@ class MetadataPluginArtistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson( json.cast(), diff --git a/lib/services/metadata/endpoints/playlist.dart b/lib/services/metadata/endpoints/playlist.dart index 09c36446..2a411ebd 100644 --- a/lib/services/metadata/endpoints/playlist.dart +++ b/lib/services/metadata/endpoints/playlist.dart @@ -33,7 +33,7 @@ class MetadataPluginPlaylistEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), diff --git a/lib/services/metadata/endpoints/user.dart b/lib/services/metadata/endpoints/user.dart index e85f1c88..11ac3f9b 100644 --- a/lib/services/metadata/endpoints/user.dart +++ b/lib/services/metadata/endpoints/user.dart @@ -30,7 +30,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullTrackObject.fromJson(json.cast()), @@ -50,7 +50,8 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject< + SpotubeSimplePlaylistObject>.fromJson( raw.cast(), (Map json) => SpotubeSimplePlaylistObject.fromJson(json.cast()), @@ -70,7 +71,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeSimpleAlbumObject.fromJson(json.cast()), @@ -90,7 +91,7 @@ class MetadataPluginUserEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), (Map json) => SpotubeFullArtistObject.fromJson(json.cast()), diff --git a/pubspec.yaml b/pubspec.yaml index 4dd74570..d221c3d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -112,7 +112,6 @@ dependencies: sliding_up_panel: ^2.0.0+1 sliver_tools: ^0.2.12 smtc_windows: ^1.0.0 - spotify: ^0.13.7 sqlite3: ^2.4.3 sqlite3_flutter_libs: ^0.5.23 stroke_text: ^0.0.2 @@ -157,6 +156,7 @@ dependencies: url: https://github.com/KRTirtho/hetu_spotube_plugin.git ref: main get_it: ^8.0.3 + spotify: ^0.13.7 dev_dependencies: build_runner: ^2.4.13 From b8cae569b44e40cabae577b8835d6ca0ecf7306c Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 15 Jun 2025 17:30:02 +0600 Subject: [PATCH 17/60] refactor: use metadata artist object for artist card and artist page --- lib/collections/fake.dart | 29 ++++++++++--------- .../horizontal_playbutton_card_view.dart | 14 +++++---- lib/modules/artist/artist_album_list.dart | 9 +++--- lib/modules/artist/artist_card.dart | 11 ++++--- lib/pages/artist/artist.dart | 25 ++++++++-------- lib/pages/artist/section/footer.dart | 5 ++-- lib/pages/artist/section/header.dart | 21 +++++++------- lib/pages/artist/section/related_artists.dart | 7 ++++- lib/pages/library/user_artists.dart | 14 +++++---- .../metadata_plugin/artist/albums.dart | 2 +- .../metadata_plugin/library/artists.dart | 4 ++- lib/provider/spotify/artist/wikipedia.dart | 4 +-- lib/provider/spotify/spotify.dart | 1 + 13 files changed, 80 insertions(+), 66 deletions(-) diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart index 840c75f9..849b4d17 100644 --- a/lib/collections/fake.dart +++ b/lib/collections/fake.dart @@ -15,17 +15,20 @@ abstract class FakeData { ..href = "text" ..total = 1; - static final Artist artist = Artist() - ..id = "1" - ..name = "Wow artist Good!" - ..images = [image] - ..popularity = 1 - ..type = "type" - ..uri = "uri" - ..externalUrls = externalUrls - ..genres = ["genre"] - ..href = "text" - ..followers = followers; + static final SpotubeFullArtistObject artist = SpotubeFullArtistObject( + id: "1", + name: "What an artist", + externalUri: "https://example.com", + followers: 10000, + genres: ["genre"], + images: [ + SpotubeImageObject( + height: 100, + width: 100, + url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg", + ), + ], + ); static final externalIds = ExternalIds() ..isrc = "text" @@ -40,7 +43,7 @@ abstract class FakeData { ..label = "label" ..popularity = 1 ..albumType = AlbumType.album - ..artists = [artist] + // ..artists = [artist] ..availableMarkets = [Market.BD] ..externalUrls = externalUrls ..href = "text" @@ -83,7 +86,7 @@ abstract class FakeData { static final Track track = Track() ..id = "1" - ..artists = [artist, artist, artist] + // ..artists = [artist, artist, artist] // ..album = albumSimple ..availableMarkets = [Market.BD] ..discNumber = 1 diff --git a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart index 385c2603..0c120e71 100644 --- a/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart +++ b/lib/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart @@ -4,7 +4,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/album/album_card.dart'; @@ -31,14 +30,16 @@ class HorizontalPlaybuttonCardView extends HookWidget { }) : assert( items.every( (item) => - item is PlaylistSimple || item is Artist || item is AlbumSimple, + item is SpotubeSimpleAlbumObject || + item is SpotubeSimplePlaylistObject || + item is SpotubeFullArtistObject, ), ); @override Widget build(BuildContext context) { final scrollController = useScrollController(); - final isArtist = items.every((s) => s is Artist); + final isArtist = items.every((s) => s is SpotubeFullArtistObject); final scale = context.theme.scaling; return Padding( @@ -99,11 +100,12 @@ class HorizontalPlaybuttonCardView extends HookWidget { final item = items[index]; return switch (item) { - PlaylistSimple() => + SpotubeSimplePlaylistObject() => PlaylistCard(item as SpotubeSimplePlaylistObject), - AlbumSimple() => + SpotubeSimpleAlbumObject() => AlbumCard(item as SpotubeSimpleAlbumObject), - Artist() => ArtistCard(item as Artist), + SpotubeFullArtistObject() => + ArtistCard(item as SpotubeFullArtistObject), _ => const SizedBox.shrink(), }; }), diff --git a/lib/modules/artist/artist_album_list.dart b/lib/modules/artist/artist_album_list.dart index 7131aa3b..c5c0defd 100644 --- a/lib/modules/artist/artist_album_list.dart +++ b/lib/modules/artist/artist_album_list.dart @@ -1,8 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/artist/albums.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class ArtistAlbumList extends HookConsumerWidget { @@ -15,15 +16,15 @@ class ArtistAlbumList extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final albumsQuery = ref.watch(artistAlbumsProvider(artistId)); + final albumsQuery = ref.watch(metadataPluginArtistAlbumsProvider(artistId)); final albumsQueryNotifier = - ref.watch(artistAlbumsProvider(artistId).notifier); + ref.watch(metadataPluginArtistAlbumsProvider(artistId).notifier); final albums = albumsQuery.asData?.value.items ?? []; final theme = Theme.of(context); - return HorizontalPlaybuttonCardView( + return HorizontalPlaybuttonCardView( isLoadingNextPage: albumsQuery.isLoadingNextPage, hasNextPage: albumsQuery.asData?.value.hasMore ?? false, items: albums, diff --git a/lib/modules/artist/artist_card.dart b/lib/modules/artist/artist_card.dart index e53070ef..d9e01206 100644 --- a/lib/modules/artist/artist_card.dart +++ b/lib/modules/artist/artist_card.dart @@ -4,16 +4,15 @@ import 'package:auto_size_text/auto_size_text.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/blacklist_provider.dart'; class ArtistCard extends HookConsumerWidget { - final Artist artist; + final SpotubeFullArtistObject artist; const ArtistCard(this.artist, {super.key}); @override @@ -36,18 +35,18 @@ class ArtistCard extends HookConsumerWidget { width: 180, child: Button.card( onPressed: () { - context.navigateTo(ArtistRoute(artistId: artist.id!)); + context.navigateTo(ArtistRoute(artistId: artist.id)); }, child: Column( children: [ Avatar( - initials: artist.name!.trim()[0].toUpperCase(), + initials: artist.name.trim()[0].toUpperCase(), provider: backgroundImage, size: 130, ), const Gap(10), AutoSizeText( - artist.name!, + artist.name, maxLines: 2, textAlign: TextAlign.center, overflow: TextOverflow.ellipsis, diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index 2037174a..1dce98e9 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -11,8 +11,9 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/pages/artist/section/footer.dart'; import 'package:spotube/pages/artist/section/header.dart'; -import 'package:spotube/pages/artist/section/related_artists.dart'; +// import 'package:spotube/pages/artist/section/related_artists.dart'; import 'package:spotube/pages/artist/section/top_tracks.dart'; +import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -31,7 +32,7 @@ class ArtistPage extends HookConsumerWidget { final scrollController = useScrollController(); final theme = Theme.of(context); - final artistQuery = ref.watch(artistProvider(artistId)); + final artistQuery = ref.watch(metadataPluginArtistProvider(artistId)); return SafeArea( bottom: false, @@ -74,16 +75,16 @@ class ArtistPage extends HookConsumerWidget { ArtistPageTopTracks(artistId: artistId), const SliverGap(20), SliverToBoxAdapter(child: ArtistAlbumList(artistId)), - SliverPadding( - padding: const EdgeInsets.all(8.0), - sliver: SliverToBoxAdapter( - child: Text( - context.l10n.fans_also_like, - style: theme.typography.h4, - ), - ), - ), - ArtistPageRelatedArtists(artistId: artistId), + // SliverPadding( + // padding: const EdgeInsets.all(8.0), + // sliver: SliverToBoxAdapter( + // child: Text( + // context.l10n.fans_also_like, + // style: theme.typography.h4, + // ), + // ), + // ), + // ArtistPageRelatedArtists(artistId: artistId), const SliverGap(20), if (artistQuery.asData?.value != null) SliverToBoxAdapter( diff --git a/lib/pages/artist/section/footer.dart b/lib/pages/artist/section/footer.dart index 9a1423b5..247f8879 100644 --- a/lib/pages/artist/section/footer.dart +++ b/lib/pages/artist/section/footer.dart @@ -1,17 +1,16 @@ import 'package:flutter/gestures.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:url_launcher/url_launcher_string.dart'; class ArtistPageFooter extends ConsumerWidget { - final Artist artist; + final SpotubeFullArtistObject artist; const ArtistPageFooter({super.key, required this.artist}); @override diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 62e66edc..6ca03a01 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -8,10 +8,11 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; +import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/utils/primitive_utils.dart'; @@ -21,7 +22,7 @@ class ArtistPageHeader extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final artistQuery = ref.watch(artistProvider(artistId)); + final artistQuery = ref.watch(metadataPluginArtistProvider(artistId)); final artist = artistQuery.asData?.value ?? FakeData.artist; final theme = Theme.of(context); @@ -30,7 +31,7 @@ class ArtistPageHeader extends HookConsumerWidget { final auth = ref.watch(authenticationProvider); ref.watch(blacklistProvider); final blacklistNotifier = ref.watch(blacklistProvider.notifier); - final isBlackListed = blacklistNotifier.containsArtist(artist); + final isBlackListed = /* blacklistNotifier.containsArtist(artist) */ false; final image = artist.images.asUrlString( placeholder: ImagePlaceholder.artist, @@ -111,13 +112,11 @@ class ArtistPageHeader extends HookConsumerWidget { IconButton.ghost( icon: const Icon(SpotubeIcons.share), onPressed: () async { - if (artist.externalUrls?.spotify != null) { - await Clipboard.setData( - ClipboardData( - text: artist.externalUrls!.spotify!, - ), - ); - } + await Clipboard.setData( + ClipboardData( + text: artist.externalUri, + ), + ); if (!context.mounted) return; @@ -199,7 +198,7 @@ class ArtistPageHeader extends HookConsumerWidget { child: AutoSizeText( context.l10n.followers( PrimitiveUtils.toReadableNumber( - artist.followers!.total!.toDouble(), + artist.followers!.toDouble(), ), ), maxLines: 1, diff --git a/lib/pages/artist/section/related_artists.dart b/lib/pages/artist/section/related_artists.dart index 2db9ca94..bc03fbe5 100644 --- a/lib/pages/artist/section/related_artists.dart +++ b/lib/pages/artist/section/related_artists.dart @@ -3,6 +3,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/modules/artist/artist_card.dart'; import 'package:spotube/provider/spotify/spotify.dart'; +@Deprecated("Related artists are no longer supported by Spotube") class ArtistPageRelatedArtists extends ConsumerWidget { final String artistId; const ArtistPageRelatedArtists({ @@ -28,7 +29,11 @@ class ArtistPageRelatedArtists extends ConsumerWidget { ), itemBuilder: (context, index) { final artist = artists.elementAt(index); - return ArtistCard(artist); + return SizedBox( + width: 180, + // child: ArtistCard(artist), + ); + // return ArtistCard(artist); }, ), ), diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index 35577cd7..81db1451 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -17,7 +17,8 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/waypoint.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @@ -28,10 +29,11 @@ class UserArtistsPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); - final artistQuery = ref.watch(followedArtistsProvider); - final artistQueryNotifier = ref.watch(followedArtistsProvider.notifier); + final artistQuery = ref.watch(metadataPluginSavedArtistsProvider); + final artistQueryNotifier = + ref.watch(metadataPluginSavedArtistsProvider.notifier); final searchText = useState(''); @@ -43,7 +45,7 @@ class UserArtistsPage extends HookConsumerWidget { } return artists .map((e) => ( - weightedRatio(e.name!, searchText.value), + weightedRatio(e.name, searchText.value), e, )) .sorted((a, b) => b.$1.compareTo(a.$1)) @@ -54,7 +56,7 @@ class UserArtistsPage extends HookConsumerWidget { final controller = useScrollController(); - if (auth.asData?.value == null) { + if (authenticated.asData?.value != true) { return const AnonymousFallback(); } diff --git a/lib/provider/metadata_plugin/artist/albums.dart b/lib/provider/metadata_plugin/artist/albums.dart index c9fd87b9..0f582bf9 100644 --- a/lib/provider/metadata_plugin/artist/albums.dart +++ b/lib/provider/metadata_plugin/artist/albums.dart @@ -24,7 +24,7 @@ class MetadataPluginArtistAlbumNotifier } } -final metadataPluginArtistAlbumsProvider = AsyncNotifierFamilyProvider< +final metadataPluginArtistAlbumsProvider = AsyncNotifierProviderFamily< MetadataPluginArtistAlbumNotifier, SpotubePaginationResponseObject, String>( diff --git a/lib/provider/metadata_plugin/library/artists.dart b/lib/provider/metadata_plugin/library/artists.dart index 5d489e00..30d0f641 100644 --- a/lib/provider/metadata_plugin/library/artists.dart +++ b/lib/provider/metadata_plugin/library/artists.dart @@ -10,10 +10,12 @@ class MetadataPluginSavedArtistNotifier int offset, int limit, ) async { - return await (await metadataPlugin).user.savedArtists( + final artists = await (await metadataPlugin).user.savedArtists( limit: limit, offset: offset, ); + + return artists; } @override diff --git a/lib/provider/spotify/artist/wikipedia.dart b/lib/provider/spotify/artist/wikipedia.dart index b2e2e6dc..7f22d5f6 100644 --- a/lib/provider/spotify/artist/wikipedia.dart +++ b/lib/provider/spotify/artist/wikipedia.dart @@ -1,8 +1,8 @@ part of '../spotify.dart'; final artistWikipediaSummaryProvider = FutureProvider.autoDispose - .family((ref, artist) async { - final query = artist.name!.replaceAll(" ", "_"); + .family((ref, artist) async { + final query = artist.name.replaceAll(" ", "_"); final res = await wikipedia.pageContent.pageSummaryTitleGet(query); if (res?.type != "standard") { diff --git a/lib/provider/spotify/spotify.dart b/lib/provider/spotify/spotify.dart index a0753fcb..d954bb8d 100644 --- a/lib/provider/spotify/spotify.dart +++ b/lib/provider/spotify/spotify.dart @@ -7,6 +7,7 @@ import 'package:drift/drift.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/env.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/logger/logger.dart'; From 7569c37739be0bcf119321d979d66b4c7409bda8 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 15 Jun 2025 22:14:04 +0600 Subject: [PATCH 18/60] refactor: home browse and browse section --- lib/collections/routes.dart | 4 +- lib/collections/routes.gr.dart | 47 +- lib/models/metadata/album.dart | 2 +- lib/models/metadata/browse.dart | 58 +- lib/models/metadata/metadata.freezed.dart | 927 +++--------------- lib/models/metadata/metadata.g.dart | 82 +- lib/modules/album/album_card.dart | 2 +- lib/modules/home/sections/feed.dart | 50 - lib/modules/home/sections/sections.dart | 46 + lib/pages/home/feed/feed_section.dart | 101 -- lib/pages/home/home.dart | 29 +- lib/pages/home/sections/section_items.dart | 122 +++ .../metadata_plugin/browse/section_items.dart | 32 + .../metadata_plugin/browse/sections.dart | 31 + .../metadata_plugin/utils/common.dart | 4 + lib/services/metadata/endpoints/browse.dart | 87 ++ lib/services/metadata/metadata.dart | 3 + 17 files changed, 530 insertions(+), 1097 deletions(-) delete mode 100644 lib/modules/home/sections/feed.dart create mode 100644 lib/modules/home/sections/sections.dart delete mode 100644 lib/pages/home/feed/feed_section.dart create mode 100644 lib/pages/home/sections/section_items.dart create mode 100644 lib/provider/metadata_plugin/browse/section_items.dart create mode 100644 lib/provider/metadata_plugin/browse/sections.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 835260bc..2c1ad8ad 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -48,8 +48,8 @@ class AppRouter extends RootStackRouter { page: GenrePlaylistsRoute.page, ), AutoRoute( - path: "home/feeds/:feedId", - page: HomeFeedSectionRoute.page, + path: "home/sections/:sectionId", + page: HomeBrowseSectionItemsRoute.page, ), AutoRoute( path: "search", diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index 798c0ad4..c97e4ce9 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -19,10 +19,10 @@ import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; import 'package:spotube/pages/connect/control/control.dart' as _i5; import 'package:spotube/pages/getting_started/getting_started.dart' as _i9; -import 'package:spotube/pages/home/feed/feed_section.dart' as _i10; import 'package:spotube/pages/home/genres/genre_playlists.dart' as _i8; import 'package:spotube/pages/home/genres/genres.dart' as _i7; import 'package:spotube/pages/home/home.dart' as _i11; +import 'package:spotube/pages/home/sections/section_items.dart' as _i10; import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i12; import 'package:spotube/pages/library/library.dart' as _i13; import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart' @@ -332,53 +332,56 @@ class GettingStartedRoute extends _i44.PageRouteInfo { } /// generated route for -/// [_i10.HomeFeedSectionPage] -class HomeFeedSectionRoute - extends _i44.PageRouteInfo { - HomeFeedSectionRoute({ +/// [_i10.HomeBrowseSectionItemsPage] +class HomeBrowseSectionItemsRoute + extends _i44.PageRouteInfo { + HomeBrowseSectionItemsRoute({ _i48.Key? key, - required String sectionUri, + required String sectionId, + required _i46.SpotubeBrowseSectionObject section, List<_i44.PageRouteInfo>? children, }) : super( - HomeFeedSectionRoute.name, - args: HomeFeedSectionRouteArgs( + HomeBrowseSectionItemsRoute.name, + args: HomeBrowseSectionItemsRouteArgs( key: key, - sectionUri: sectionUri, + sectionId: sectionId, + section: section, ), - rawPathParams: {'feedId': sectionUri}, + rawPathParams: {'sectionId': sectionId}, initialChildren: children, ); - static const String name = 'HomeFeedSectionRoute'; + static const String name = 'HomeBrowseSectionItemsRoute'; static _i44.PageInfo page = _i44.PageInfo( name, builder: (data) { - final pathParams = data.inheritedPathParams; - final args = data.argsAs( - orElse: () => HomeFeedSectionRouteArgs( - sectionUri: pathParams.getString('feedId'))); - return _i10.HomeFeedSectionPage( + final args = data.argsAs(); + return _i10.HomeBrowseSectionItemsPage( key: args.key, - sectionUri: args.sectionUri, + sectionId: args.sectionId, + section: args.section, ); }, ); } -class HomeFeedSectionRouteArgs { - const HomeFeedSectionRouteArgs({ +class HomeBrowseSectionItemsRouteArgs { + const HomeBrowseSectionItemsRouteArgs({ this.key, - required this.sectionUri, + required this.sectionId, + required this.section, }); final _i48.Key? key; - final String sectionUri; + final String sectionId; + + final _i46.SpotubeBrowseSectionObject section; @override String toString() { - return 'HomeFeedSectionRouteArgs{key: $key, sectionUri: $sectionUri}'; + return 'HomeBrowseSectionItemsRouteArgs{key: $key, sectionId: $sectionId, section: $section}'; } } diff --git a/lib/models/metadata/album.dart b/lib/models/metadata/album.dart index da2c21fd..bc9022de 100644 --- a/lib/models/metadata/album.dart +++ b/lib/models/metadata/album.dart @@ -33,8 +33,8 @@ class SpotubeSimpleAlbumObject with _$SpotubeSimpleAlbumObject { required String externalUri, required List artists, @Default([]) List images, - required String releaseDate, required SpotubeAlbumType albumType, + String? releaseDate, }) = _SpotubeSimpleAlbumObject; factory SpotubeSimpleAlbumObject.fromJson(Map json) => diff --git a/lib/models/metadata/browse.dart b/lib/models/metadata/browse.dart index fc323d01..e2a69181 100644 --- a/lib/models/metadata/browse.dart +++ b/lib/models/metadata/browse.dart @@ -1,43 +1,21 @@ part of 'metadata.dart'; -enum SectionItemType { - @JsonValue("Playlist") - playlist, - @JsonValue("Album") - album, - @JsonValue("Artist") - artist -} - -@Freezed(unionKey: "itemType") -class SpotubeBrowseSectionObject with _$SpotubeBrowseSectionObject { - @FreezedUnionValue("Album") - factory SpotubeBrowseSectionObject.album({ - required String id, - required String title, - required String externalUri, - required SectionItemType itemType, - required List items, - }) = SpotubeBrowseAlbumSectionObject; - - @FreezedUnionValue("Artist") - factory SpotubeBrowseSectionObject.artist({ - required String id, - required String title, - required String externalUri, - required SectionItemType itemType, - required List items, - }) = SpotubeBrowseArtistSectionObject; - - @FreezedUnionValue("Playlist") - factory SpotubeBrowseSectionObject.playlist({ - required String id, - required String title, - required String externalUri, - required SectionItemType itemType, - required List items, - }) = SpotubeBrowsePlaylistSectionObject; - - factory SpotubeBrowseSectionObject.fromJson(Map json) => - _$SpotubeBrowseSectionObjectFromJson(json); +@Freezed(genericArgumentFactories: true) +class SpotubeBrowseSectionObject with _$SpotubeBrowseSectionObject { + factory SpotubeBrowseSectionObject({ + required String id, + required String title, + required String externalUri, + required bool browseMore, + required List items, + }) = _SpotubeBrowseSectionObject; + + factory SpotubeBrowseSectionObject.fromJson( + Map json, + T Function(Map json) fromJsonT, + ) => + _$SpotubeBrowseSectionObjectFromJson( + json, + (json) => fromJsonT(json as Map), + ); } diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 7774224c..2bf50c6c 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -406,8 +406,8 @@ mixin _$SpotubeSimpleAlbumObject { List get artists => throw _privateConstructorUsedError; List get images => throw _privateConstructorUsedError; - String get releaseDate => throw _privateConstructorUsedError; SpotubeAlbumType get albumType => throw _privateConstructorUsedError; + String? get releaseDate => throw _privateConstructorUsedError; /// Serializes this SpotubeSimpleAlbumObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -431,8 +431,8 @@ abstract class $SpotubeSimpleAlbumObjectCopyWith<$Res> { String externalUri, List artists, List images, - String releaseDate, - SpotubeAlbumType albumType}); + SpotubeAlbumType albumType, + String? releaseDate}); } /// @nodoc @@ -456,8 +456,8 @@ class _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, Object? externalUri = null, Object? artists = null, Object? images = null, - Object? releaseDate = null, Object? albumType = null, + Object? releaseDate = freezed, }) { return _then(_value.copyWith( id: null == id @@ -480,14 +480,14 @@ class _$SpotubeSimpleAlbumObjectCopyWithImpl<$Res, ? _value.images : images // ignore: cast_nullable_to_non_nullable as List, - releaseDate: null == releaseDate - ? _value.releaseDate - : releaseDate // ignore: cast_nullable_to_non_nullable - as String, albumType: null == albumType ? _value.albumType : albumType // ignore: cast_nullable_to_non_nullable as SpotubeAlbumType, + releaseDate: freezed == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable + as String?, ) as $Val); } } @@ -507,8 +507,8 @@ abstract class _$$SpotubeSimpleAlbumObjectImplCopyWith<$Res> String externalUri, List artists, List images, - String releaseDate, - SpotubeAlbumType albumType}); + SpotubeAlbumType albumType, + String? releaseDate}); } /// @nodoc @@ -531,8 +531,8 @@ class __$$SpotubeSimpleAlbumObjectImplCopyWithImpl<$Res> Object? externalUri = null, Object? artists = null, Object? images = null, - Object? releaseDate = null, Object? albumType = null, + Object? releaseDate = freezed, }) { return _then(_$SpotubeSimpleAlbumObjectImpl( id: null == id @@ -555,14 +555,14 @@ class __$$SpotubeSimpleAlbumObjectImplCopyWithImpl<$Res> ? _value._images : images // ignore: cast_nullable_to_non_nullable as List, - releaseDate: null == releaseDate - ? _value.releaseDate - : releaseDate // ignore: cast_nullable_to_non_nullable - as String, albumType: null == albumType ? _value.albumType : albumType // ignore: cast_nullable_to_non_nullable as SpotubeAlbumType, + releaseDate: freezed == releaseDate + ? _value.releaseDate + : releaseDate // ignore: cast_nullable_to_non_nullable + as String?, )); } } @@ -576,8 +576,8 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { required this.externalUri, required final List artists, final List images = const [], - required this.releaseDate, - required this.albumType}) + required this.albumType, + this.releaseDate}) : _artists = artists, _images = images; @@ -607,14 +607,14 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { return EqualUnmodifiableListView(_images); } - @override - final String releaseDate; @override final SpotubeAlbumType albumType; + @override + final String? releaseDate; @override String toString() { - return 'SpotubeSimpleAlbumObject(id: $id, name: $name, externalUri: $externalUri, artists: $artists, images: $images, releaseDate: $releaseDate, albumType: $albumType)'; + return 'SpotubeSimpleAlbumObject(id: $id, name: $name, externalUri: $externalUri, artists: $artists, images: $images, albumType: $albumType, releaseDate: $releaseDate)'; } @override @@ -628,10 +628,10 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { other.externalUri == externalUri) && const DeepCollectionEquality().equals(other._artists, _artists) && const DeepCollectionEquality().equals(other._images, _images) && - (identical(other.releaseDate, releaseDate) || - other.releaseDate == releaseDate) && (identical(other.albumType, albumType) || - other.albumType == albumType)); + other.albumType == albumType) && + (identical(other.releaseDate, releaseDate) || + other.releaseDate == releaseDate)); } @JsonKey(includeFromJson: false, includeToJson: false) @@ -643,8 +643,8 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { externalUri, const DeepCollectionEquality().hash(_artists), const DeepCollectionEquality().hash(_images), - releaseDate, - albumType); + albumType, + releaseDate); /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @@ -665,14 +665,13 @@ class _$SpotubeSimpleAlbumObjectImpl implements _SpotubeSimpleAlbumObject { abstract class _SpotubeSimpleAlbumObject implements SpotubeSimpleAlbumObject { factory _SpotubeSimpleAlbumObject( - {required final String id, - required final String name, - required final String externalUri, - required final List artists, - final List images, - required final String releaseDate, - required final SpotubeAlbumType albumType}) = - _$SpotubeSimpleAlbumObjectImpl; + {required final String id, + required final String name, + required final String externalUri, + required final List artists, + final List images, + required final SpotubeAlbumType albumType, + final String? releaseDate}) = _$SpotubeSimpleAlbumObjectImpl; factory _SpotubeSimpleAlbumObject.fromJson(Map json) = _$SpotubeSimpleAlbumObjectImpl.fromJson; @@ -688,9 +687,9 @@ abstract class _SpotubeSimpleAlbumObject implements SpotubeSimpleAlbumObject { @override List get images; @override - String get releaseDate; - @override SpotubeAlbumType get albumType; + @override + String? get releaseDate; /// Create a copy of SpotubeSimpleAlbumObject /// with the given fields replaced by the non-null parameter values. @@ -1177,121 +1176,50 @@ abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject { get copyWith => throw _privateConstructorUsedError; } -SpotubeBrowseSectionObject _$SpotubeBrowseSectionObjectFromJson( - Map json) { - switch (json['itemType']) { - case 'Album': - return SpotubeBrowseAlbumSectionObject.fromJson(json); - case 'Artist': - return SpotubeBrowseArtistSectionObject.fromJson(json); - case 'Playlist': - return SpotubeBrowsePlaylistSectionObject.fromJson(json); - - default: - throw CheckedFromJsonException( - json, - 'itemType', - 'SpotubeBrowseSectionObject', - 'Invalid union type "${json['itemType']}"!'); - } +SpotubeBrowseSectionObject _$SpotubeBrowseSectionObjectFromJson( + Map json, T Function(Object?) fromJsonT) { + return _SpotubeBrowseSectionObject.fromJson(json, fromJsonT); } /// @nodoc -mixin _$SpotubeBrowseSectionObject { +mixin _$SpotubeBrowseSectionObject { String get id => throw _privateConstructorUsedError; String get title => throw _privateConstructorUsedError; String get externalUri => throw _privateConstructorUsedError; - SectionItemType get itemType => throw _privateConstructorUsedError; - List get items => throw _privateConstructorUsedError; - @optionalTypeArgs - TResult when({ - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - album, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - artist, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - playlist, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult map({ - required TResult Function(SpotubeBrowseAlbumSectionObject value) album, - required TResult Function(SpotubeBrowseArtistSectionObject value) artist, - required TResult Function(SpotubeBrowsePlaylistSectionObject value) - playlist, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - }) => - throw _privateConstructorUsedError; - @optionalTypeArgs - TResult maybeMap({ - TResult Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - required TResult orElse(), - }) => - throw _privateConstructorUsedError; + bool get browseMore => throw _privateConstructorUsedError; + List get items => throw _privateConstructorUsedError; /// Serializes this SpotubeBrowseSectionObject to a JSON map. - Map toJson() => throw _privateConstructorUsedError; + Map toJson(Object? Function(T) toJsonT) => + throw _privateConstructorUsedError; /// Create a copy of SpotubeBrowseSectionObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) - $SpotubeBrowseSectionObjectCopyWith + $SpotubeBrowseSectionObjectCopyWith> get copyWith => throw _privateConstructorUsedError; } /// @nodoc -abstract class $SpotubeBrowseSectionObjectCopyWith<$Res> { - factory $SpotubeBrowseSectionObjectCopyWith(SpotubeBrowseSectionObject value, - $Res Function(SpotubeBrowseSectionObject) then) = - _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, - SpotubeBrowseSectionObject>; +abstract class $SpotubeBrowseSectionObjectCopyWith { + factory $SpotubeBrowseSectionObjectCopyWith( + SpotubeBrowseSectionObject value, + $Res Function(SpotubeBrowseSectionObject) then) = + _$SpotubeBrowseSectionObjectCopyWithImpl>; @useResult $Res call( - {String id, String title, String externalUri, SectionItemType itemType}); + {String id, + String title, + String externalUri, + bool browseMore, + List items}); } /// @nodoc -class _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, - $Val extends SpotubeBrowseSectionObject> - implements $SpotubeBrowseSectionObjectCopyWith<$Res> { +class _$SpotubeBrowseSectionObjectCopyWithImpl> + implements $SpotubeBrowseSectionObjectCopyWith { _$SpotubeBrowseSectionObjectCopyWithImpl(this._value, this._then); // ignore: unused_field @@ -1307,7 +1235,8 @@ class _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, Object? id = null, Object? title = null, Object? externalUri = null, - Object? itemType = null, + Object? browseMore = null, + Object? items = null, }) { return _then(_value.copyWith( id: null == id @@ -1322,39 +1251,43 @@ class _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, ? _value.externalUri : externalUri // ignore: cast_nullable_to_non_nullable as String, - itemType: null == itemType - ? _value.itemType - : itemType // ignore: cast_nullable_to_non_nullable - as SectionItemType, + browseMore: null == browseMore + ? _value.browseMore + : browseMore // 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 _$$SpotubeBrowseAlbumSectionObjectImplCopyWith<$Res> - implements $SpotubeBrowseSectionObjectCopyWith<$Res> { - factory _$$SpotubeBrowseAlbumSectionObjectImplCopyWith( - _$SpotubeBrowseAlbumSectionObjectImpl value, - $Res Function(_$SpotubeBrowseAlbumSectionObjectImpl) then) = - __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res>; +abstract class _$$SpotubeBrowseSectionObjectImplCopyWith + implements $SpotubeBrowseSectionObjectCopyWith { + factory _$$SpotubeBrowseSectionObjectImplCopyWith( + _$SpotubeBrowseSectionObjectImpl value, + $Res Function(_$SpotubeBrowseSectionObjectImpl) then) = + __$$SpotubeBrowseSectionObjectImplCopyWithImpl; @override @useResult $Res call( {String id, String title, String externalUri, - SectionItemType itemType, - List items}); + bool browseMore, + List items}); } /// @nodoc -class __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res> - extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, - _$SpotubeBrowseAlbumSectionObjectImpl> - implements _$$SpotubeBrowseAlbumSectionObjectImplCopyWith<$Res> { - __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl( - _$SpotubeBrowseAlbumSectionObjectImpl _value, - $Res Function(_$SpotubeBrowseAlbumSectionObjectImpl) _then) +class __$$SpotubeBrowseSectionObjectImplCopyWithImpl + extends _$SpotubeBrowseSectionObjectCopyWithImpl> + implements _$$SpotubeBrowseSectionObjectImplCopyWith { + __$$SpotubeBrowseSectionObjectImplCopyWithImpl( + _$SpotubeBrowseSectionObjectImpl _value, + $Res Function(_$SpotubeBrowseSectionObjectImpl) _then) : super(_value, _then); /// Create a copy of SpotubeBrowseSectionObject @@ -1365,10 +1298,10 @@ class __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res> Object? id = null, Object? title = null, Object? externalUri = null, - Object? itemType = null, + Object? browseMore = null, Object? items = null, }) { - return _then(_$SpotubeBrowseAlbumSectionObjectImpl( + return _then(_$SpotubeBrowseSectionObjectImpl( id: null == id ? _value.id : id // ignore: cast_nullable_to_non_nullable @@ -1381,33 +1314,33 @@ class __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl<$Res> ? _value.externalUri : externalUri // ignore: cast_nullable_to_non_nullable as String, - itemType: null == itemType - ? _value.itemType - : itemType // ignore: cast_nullable_to_non_nullable - as SectionItemType, + browseMore: null == browseMore + ? _value.browseMore + : browseMore // ignore: cast_nullable_to_non_nullable + as bool, items: null == items ? _value._items : items // ignore: cast_nullable_to_non_nullable - as List, + as List, )); } } /// @nodoc -@JsonSerializable() -class _$SpotubeBrowseAlbumSectionObjectImpl - implements SpotubeBrowseAlbumSectionObject { - _$SpotubeBrowseAlbumSectionObjectImpl( +@JsonSerializable(genericArgumentFactories: true) +class _$SpotubeBrowseSectionObjectImpl + implements _SpotubeBrowseSectionObject { + _$SpotubeBrowseSectionObjectImpl( {required this.id, required this.title, required this.externalUri, - required this.itemType, - required final List items}) + required this.browseMore, + required final List items}) : _items = items; - factory _$SpotubeBrowseAlbumSectionObjectImpl.fromJson( - Map json) => - _$$SpotubeBrowseAlbumSectionObjectImplFromJson(json); + factory _$SpotubeBrowseSectionObjectImpl.fromJson( + Map json, T Function(Object?) fromJsonT) => + _$$SpotubeBrowseSectionObjectImplFromJson(json, fromJsonT); @override final String id; @@ -1416,10 +1349,10 @@ class _$SpotubeBrowseAlbumSectionObjectImpl @override final String externalUri; @override - final SectionItemType itemType; - final List _items; + final bool browseMore; + final List _items; @override - List get items { + List get items { if (_items is EqualUnmodifiableListView) return _items; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_items); @@ -1427,145 +1360,56 @@ class _$SpotubeBrowseAlbumSectionObjectImpl @override String toString() { - return 'SpotubeBrowseSectionObject.album(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; + return 'SpotubeBrowseSectionObject<$T>(id: $id, title: $title, externalUri: $externalUri, browseMore: $browseMore, items: $items)'; } @override bool operator ==(Object other) { return identical(this, other) || (other.runtimeType == runtimeType && - other is _$SpotubeBrowseAlbumSectionObjectImpl && + other is _$SpotubeBrowseSectionObjectImpl && (identical(other.id, id) || other.id == id) && (identical(other.title, title) || other.title == title) && (identical(other.externalUri, externalUri) || other.externalUri == externalUri) && - (identical(other.itemType, itemType) || - other.itemType == itemType) && + (identical(other.browseMore, browseMore) || + other.browseMore == browseMore) && const DeepCollectionEquality().equals(other._items, _items)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, - const DeepCollectionEquality().hash(_items)); + int get hashCode => Object.hash(runtimeType, id, title, externalUri, + browseMore, const DeepCollectionEquality().hash(_items)); /// Create a copy of SpotubeBrowseSectionObject /// with the given fields replaced by the non-null parameter values. @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') - _$$SpotubeBrowseAlbumSectionObjectImplCopyWith< - _$SpotubeBrowseAlbumSectionObjectImpl> - get copyWith => __$$SpotubeBrowseAlbumSectionObjectImplCopyWithImpl< - _$SpotubeBrowseAlbumSectionObjectImpl>(this, _$identity); + _$$SpotubeBrowseSectionObjectImplCopyWith> + get copyWith => __$$SpotubeBrowseSectionObjectImplCopyWithImpl>(this, _$identity); @override - @optionalTypeArgs - TResult when({ - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - album, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - artist, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - playlist, - }) { - return album(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - }) { - return album?.call(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - required TResult orElse(), - }) { - if (album != null) { - return album(id, title, externalUri, itemType, items); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(SpotubeBrowseAlbumSectionObject value) album, - required TResult Function(SpotubeBrowseArtistSectionObject value) artist, - required TResult Function(SpotubeBrowsePlaylistSectionObject value) - playlist, - }) { - return album(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - }) { - return album?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - required TResult orElse(), - }) { - if (album != null) { - return album(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$SpotubeBrowseAlbumSectionObjectImplToJson( - this, - ); + Map toJson(Object? Function(T) toJsonT) { + return _$$SpotubeBrowseSectionObjectImplToJson(this, toJsonT); } } -abstract class SpotubeBrowseAlbumSectionObject - implements SpotubeBrowseSectionObject { - factory SpotubeBrowseAlbumSectionObject( - {required final String id, - required final String title, - required final String externalUri, - required final SectionItemType itemType, - required final List items}) = - _$SpotubeBrowseAlbumSectionObjectImpl; +abstract class _SpotubeBrowseSectionObject + implements SpotubeBrowseSectionObject { + factory _SpotubeBrowseSectionObject( + {required final String id, + required final String title, + required final String externalUri, + required final bool browseMore, + required final List items}) = _$SpotubeBrowseSectionObjectImpl; - factory SpotubeBrowseAlbumSectionObject.fromJson(Map json) = - _$SpotubeBrowseAlbumSectionObjectImpl.fromJson; + factory _SpotubeBrowseSectionObject.fromJson( + Map json, T Function(Object?) fromJsonT) = + _$SpotubeBrowseSectionObjectImpl.fromJson; @override String get id; @@ -1574,531 +1418,16 @@ abstract class SpotubeBrowseAlbumSectionObject @override String get externalUri; @override - SectionItemType get itemType; + bool get browseMore; @override - List get items; + List get items; /// Create a copy of SpotubeBrowseSectionObject /// with the given fields replaced by the non-null parameter values. @override @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeBrowseAlbumSectionObjectImplCopyWith< - _$SpotubeBrowseAlbumSectionObjectImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$SpotubeBrowseArtistSectionObjectImplCopyWith<$Res> - implements $SpotubeBrowseSectionObjectCopyWith<$Res> { - factory _$$SpotubeBrowseArtistSectionObjectImplCopyWith( - _$SpotubeBrowseArtistSectionObjectImpl value, - $Res Function(_$SpotubeBrowseArtistSectionObjectImpl) then) = - __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String title, - String externalUri, - SectionItemType itemType, - List items}); -} - -/// @nodoc -class __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl<$Res> - extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, - _$SpotubeBrowseArtistSectionObjectImpl> - implements _$$SpotubeBrowseArtistSectionObjectImplCopyWith<$Res> { - __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl( - _$SpotubeBrowseArtistSectionObjectImpl _value, - $Res Function(_$SpotubeBrowseArtistSectionObjectImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? externalUri = null, - Object? itemType = null, - Object? items = null, - }) { - return _then(_$SpotubeBrowseArtistSectionObjectImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - externalUri: null == externalUri - ? _value.externalUri - : externalUri // ignore: cast_nullable_to_non_nullable - as String, - itemType: null == itemType - ? _value.itemType - : itemType // ignore: cast_nullable_to_non_nullable - as SectionItemType, - items: null == items - ? _value._items - : items // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotubeBrowseArtistSectionObjectImpl - implements SpotubeBrowseArtistSectionObject { - _$SpotubeBrowseArtistSectionObjectImpl( - {required this.id, - required this.title, - required this.externalUri, - required this.itemType, - required final List items}) - : _items = items; - - factory _$SpotubeBrowseArtistSectionObjectImpl.fromJson( - Map json) => - _$$SpotubeBrowseArtistSectionObjectImplFromJson(json); - - @override - final String id; - @override - final String title; - @override - final String externalUri; - @override - final SectionItemType itemType; - final List _items; - @override - List get items { - if (_items is EqualUnmodifiableListView) return _items; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_items); - } - - @override - String toString() { - return 'SpotubeBrowseSectionObject.artist(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotubeBrowseArtistSectionObjectImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - (identical(other.externalUri, externalUri) || - other.externalUri == externalUri) && - (identical(other.itemType, itemType) || - other.itemType == itemType) && - const DeepCollectionEquality().equals(other._items, _items)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, - const DeepCollectionEquality().hash(_items)); - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotubeBrowseArtistSectionObjectImplCopyWith< - _$SpotubeBrowseArtistSectionObjectImpl> - get copyWith => __$$SpotubeBrowseArtistSectionObjectImplCopyWithImpl< - _$SpotubeBrowseArtistSectionObjectImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - album, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - artist, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - playlist, - }) { - return artist(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - }) { - return artist?.call(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - required TResult orElse(), - }) { - if (artist != null) { - return artist(id, title, externalUri, itemType, items); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(SpotubeBrowseAlbumSectionObject value) album, - required TResult Function(SpotubeBrowseArtistSectionObject value) artist, - required TResult Function(SpotubeBrowsePlaylistSectionObject value) - playlist, - }) { - return artist(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - }) { - return artist?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - required TResult orElse(), - }) { - if (artist != null) { - return artist(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$SpotubeBrowseArtistSectionObjectImplToJson( - this, - ); - } -} - -abstract class SpotubeBrowseArtistSectionObject - implements SpotubeBrowseSectionObject { - factory SpotubeBrowseArtistSectionObject( - {required final String id, - required final String title, - required final String externalUri, - required final SectionItemType itemType, - required final List items}) = - _$SpotubeBrowseArtistSectionObjectImpl; - - factory SpotubeBrowseArtistSectionObject.fromJson(Map json) = - _$SpotubeBrowseArtistSectionObjectImpl.fromJson; - - @override - String get id; - @override - String get title; - @override - String get externalUri; - @override - SectionItemType get itemType; - @override - List get items; - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeBrowseArtistSectionObjectImplCopyWith< - _$SpotubeBrowseArtistSectionObjectImpl> - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith<$Res> - implements $SpotubeBrowseSectionObjectCopyWith<$Res> { - factory _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith( - _$SpotubeBrowsePlaylistSectionObjectImpl value, - $Res Function(_$SpotubeBrowsePlaylistSectionObjectImpl) then) = - __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String title, - String externalUri, - SectionItemType itemType, - List items}); -} - -/// @nodoc -class __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl<$Res> - extends _$SpotubeBrowseSectionObjectCopyWithImpl<$Res, - _$SpotubeBrowsePlaylistSectionObjectImpl> - implements _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith<$Res> { - __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl( - _$SpotubeBrowsePlaylistSectionObjectImpl _value, - $Res Function(_$SpotubeBrowsePlaylistSectionObjectImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? id = null, - Object? title = null, - Object? externalUri = null, - Object? itemType = null, - Object? items = null, - }) { - return _then(_$SpotubeBrowsePlaylistSectionObjectImpl( - id: null == id - ? _value.id - : id // ignore: cast_nullable_to_non_nullable - as String, - title: null == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String, - externalUri: null == externalUri - ? _value.externalUri - : externalUri // ignore: cast_nullable_to_non_nullable - as String, - itemType: null == itemType - ? _value.itemType - : itemType // ignore: cast_nullable_to_non_nullable - as SectionItemType, - items: null == items - ? _value._items - : items // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotubeBrowsePlaylistSectionObjectImpl - implements SpotubeBrowsePlaylistSectionObject { - _$SpotubeBrowsePlaylistSectionObjectImpl( - {required this.id, - required this.title, - required this.externalUri, - required this.itemType, - required final List items}) - : _items = items; - - factory _$SpotubeBrowsePlaylistSectionObjectImpl.fromJson( - Map json) => - _$$SpotubeBrowsePlaylistSectionObjectImplFromJson(json); - - @override - final String id; - @override - final String title; - @override - final String externalUri; - @override - final SectionItemType itemType; - final List _items; - @override - List get items { - if (_items is EqualUnmodifiableListView) return _items; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_items); - } - - @override - String toString() { - return 'SpotubeBrowseSectionObject.playlist(id: $id, title: $title, externalUri: $externalUri, itemType: $itemType, items: $items)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotubeBrowsePlaylistSectionObjectImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.title, title) || other.title == title) && - (identical(other.externalUri, externalUri) || - other.externalUri == externalUri) && - (identical(other.itemType, itemType) || - other.itemType == itemType) && - const DeepCollectionEquality().equals(other._items, _items)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, id, title, externalUri, itemType, - const DeepCollectionEquality().hash(_items)); - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith< - _$SpotubeBrowsePlaylistSectionObjectImpl> - get copyWith => __$$SpotubeBrowsePlaylistSectionObjectImplCopyWithImpl< - _$SpotubeBrowsePlaylistSectionObjectImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - album, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - artist, - required TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items) - playlist, - }) { - return playlist(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult? Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - }) { - return playlist?.call(id, title, externalUri, itemType, items); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - album, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - artist, - TResult Function(String id, String title, String externalUri, - SectionItemType itemType, List items)? - playlist, - required TResult orElse(), - }) { - if (playlist != null) { - return playlist(id, title, externalUri, itemType, items); - } - return orElse(); - } - - @override - @optionalTypeArgs - TResult map({ - required TResult Function(SpotubeBrowseAlbumSectionObject value) album, - required TResult Function(SpotubeBrowseArtistSectionObject value) artist, - required TResult Function(SpotubeBrowsePlaylistSectionObject value) - playlist, - }) { - return playlist(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult? Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult? Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - }) { - return playlist?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(SpotubeBrowseAlbumSectionObject value)? album, - TResult Function(SpotubeBrowseArtistSectionObject value)? artist, - TResult Function(SpotubeBrowsePlaylistSectionObject value)? playlist, - required TResult orElse(), - }) { - if (playlist != null) { - return playlist(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$SpotubeBrowsePlaylistSectionObjectImplToJson( - this, - ); - } -} - -abstract class SpotubeBrowsePlaylistSectionObject - implements SpotubeBrowseSectionObject { - factory SpotubeBrowsePlaylistSectionObject( - {required final String id, - required final String title, - required final String externalUri, - required final SectionItemType itemType, - required final List items}) = - _$SpotubeBrowsePlaylistSectionObjectImpl; - - factory SpotubeBrowsePlaylistSectionObject.fromJson( - Map json) = - _$SpotubeBrowsePlaylistSectionObjectImpl.fromJson; - - @override - String get id; - @override - String get title; - @override - String get externalUri; - @override - SectionItemType get itemType; - @override - List get items; - - /// Create a copy of SpotubeBrowseSectionObject - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeBrowsePlaylistSectionObjectImplCopyWith< - _$SpotubeBrowsePlaylistSectionObjectImpl> + _$$SpotubeBrowseSectionObjectImplCopyWith> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 5bd63591..52001504 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -64,8 +64,8 @@ _$SpotubeSimpleAlbumObjectImpl _$$SpotubeSimpleAlbumObjectImplFromJson( Map.from(e as Map))) .toList() ?? const [], - releaseDate: json['releaseDate'] as String, albumType: $enumDecode(_$SpotubeAlbumTypeEnumMap, json['albumType']), + releaseDate: json['releaseDate'] as String?, ); Map _$$SpotubeSimpleAlbumObjectImplToJson( @@ -76,8 +76,8 @@ Map _$$SpotubeSimpleAlbumObjectImplToJson( 'externalUri': instance.externalUri, 'artists': instance.artists.map((e) => e.toJson()).toList(), 'images': instance.images.map((e) => e.toJson()).toList(), - 'releaseDate': instance.releaseDate, 'albumType': _$SpotubeAlbumTypeEnumMap[instance.albumType]!, + 'releaseDate': instance.releaseDate, }; _$SpotubeFullArtistObjectImpl _$$SpotubeFullArtistObjectImplFromJson( @@ -123,79 +123,29 @@ Map _$$SpotubeSimpleArtistObjectImplToJson( 'externalUri': instance.externalUri, }; -_$SpotubeBrowseAlbumSectionObjectImpl - _$$SpotubeBrowseAlbumSectionObjectImplFromJson(Map json) => - _$SpotubeBrowseAlbumSectionObjectImpl( +_$SpotubeBrowseSectionObjectImpl + _$$SpotubeBrowseSectionObjectImplFromJson( + Map json, + T Function(Object? json) fromJsonT, +) => + _$SpotubeBrowseSectionObjectImpl( id: json['id'] as String, title: json['title'] as String, externalUri: json['externalUri'] as String, - itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), - items: (json['items'] as List) - .map((e) => SpotubeSimpleAlbumObject.fromJson( - Map.from(e as Map))) - .toList(), + browseMore: json['browseMore'] as bool, + items: (json['items'] as List).map(fromJsonT).toList(), ); -Map _$$SpotubeBrowseAlbumSectionObjectImplToJson( - _$SpotubeBrowseAlbumSectionObjectImpl instance) => +Map _$$SpotubeBrowseSectionObjectImplToJson( + _$SpotubeBrowseSectionObjectImpl instance, + Object? Function(T value) toJsonT, +) => { 'id': instance.id, 'title': instance.title, 'externalUri': instance.externalUri, - 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, - 'items': instance.items.map((e) => e.toJson()).toList(), - }; - -const _$SectionItemTypeEnumMap = { - SectionItemType.playlist: 'Playlist', - SectionItemType.album: 'Album', - SectionItemType.artist: 'Artist', -}; - -_$SpotubeBrowseArtistSectionObjectImpl - _$$SpotubeBrowseArtistSectionObjectImplFromJson(Map json) => - _$SpotubeBrowseArtistSectionObjectImpl( - id: json['id'] as String, - title: json['title'] as String, - externalUri: json['externalUri'] as String, - itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), - items: (json['items'] as List) - .map((e) => SpotubeSimpleArtistObject.fromJson( - Map.from(e as Map))) - .toList(), - ); - -Map _$$SpotubeBrowseArtistSectionObjectImplToJson( - _$SpotubeBrowseArtistSectionObjectImpl instance) => - { - 'id': instance.id, - 'title': instance.title, - 'externalUri': instance.externalUri, - 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, - 'items': instance.items.map((e) => e.toJson()).toList(), - }; - -_$SpotubeBrowsePlaylistSectionObjectImpl - _$$SpotubeBrowsePlaylistSectionObjectImplFromJson(Map json) => - _$SpotubeBrowsePlaylistSectionObjectImpl( - id: json['id'] as String, - title: json['title'] as String, - externalUri: json['externalUri'] as String, - itemType: $enumDecode(_$SectionItemTypeEnumMap, json['itemType']), - items: (json['items'] as List) - .map((e) => SpotubeSimplePlaylistObject.fromJson( - Map.from(e as Map))) - .toList(), - ); - -Map _$$SpotubeBrowsePlaylistSectionObjectImplToJson( - _$SpotubeBrowsePlaylistSectionObjectImpl instance) => - { - 'id': instance.id, - 'title': instance.title, - 'externalUri': instance.externalUri, - 'itemType': _$SectionItemTypeEnumMap[instance.itemType]!, - 'items': instance.items.map((e) => e.toJson()).toList(), + 'browseMore': instance.browseMore, + 'items': instance.items.map(toJsonT).toList(), }; _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index 33a7dc01..0ea07f51 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -63,7 +63,7 @@ class AlbumCard extends HookConsumerWidget { ); var isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - var description = "${album.albumType} • ${album.artists.asString()}"; + var description = "${album.albumType.name} • ${album.artists.asString()}"; void onTap() { context.navigateTo(AlbumRoute(id: album.id, album: album)); diff --git a/lib/modules/home/sections/feed.dart b/lib/modules/home/sections/feed.dart deleted file mode 100644 index 216dc491..00000000 --- a/lib/modules/home/sections/feed.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/spotify/views/home.dart'; - -class HomePageFeedSection extends HookConsumerWidget { - const HomePageFeedSection({super.key}); - - @override - Widget build(BuildContext context, ref) { - final homeFeed = ref.watch(homeViewProvider); - final nonShortSections = homeFeed.asData?.value.sections - .where((s) => s.typename == "HomeGenericSectionData") - .toList() ?? - []; - - return SliverList.builder( - itemCount: nonShortSections.length, - itemBuilder: (context, index) { - final section = nonShortSections[index]; - if (section.items.isEmpty) return const SizedBox.shrink(); - - return HorizontalPlaybuttonCardView( - items: [ - for (final item in section.items) - if (item.album != null) - item.album!.asAlbum - else if (item.artist != null) - item.artist!.asArtist - else if (item.playlist != null) - item.playlist!.asPlaylist - ], - title: Text(section.title ?? context.l10n.no_title), - hasNextPage: false, - isLoadingNextPage: false, - onFetchMore: () {}, - titleTrailing: Button.text( - child: Text(context.l10n.browse_all), - onPressed: () { - context.navigateTo(HomeFeedSectionRoute(sectionUri: section.uri)); - }, - ), - ); - }, - ); - } -} diff --git a/lib/modules/home/sections/sections.dart b/lib/modules/home/sections/sections.dart new file mode 100644 index 00000000..cc7a7fad --- /dev/null +++ b/lib/modules/home/sections/sections.dart @@ -0,0 +1,46 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/metadata_plugin/browse/sections.dart'; + +class HomePageBrowseSection extends HookConsumerWidget { + const HomePageBrowseSection({super.key}); + + @override + Widget build(BuildContext context, ref) { + final browseSections = ref.watch(metadataPluginBrowseSectionsProvider); + final sections = browseSections.asData?.value.items; + + return SliverList.builder( + itemCount: sections?.length ?? 0, + itemBuilder: (context, index) { + final section = sections![index]; + if (section.items.isEmpty) return const SizedBox.shrink(); + + return HorizontalPlaybuttonCardView( + items: section.items, + title: Text(section.title), + hasNextPage: false, + isLoadingNextPage: false, + onFetchMore: () {}, + titleTrailing: section.browseMore + ? Button.text( + child: Text(context.l10n.browse_all), + onPressed: () { + context.navigateTo( + HomeBrowseSectionItemsRoute( + sectionId: section.id, + section: section, + ), + ); + }, + ) + : null, + ); + }, + ); + } +} diff --git a/lib/pages/home/feed/feed_section.dart b/lib/pages/home/feed/feed_section.dart deleted file mode 100644 index 5ece363d..00000000 --- a/lib/pages/home/feed/feed_section.dart +++ /dev/null @@ -1,101 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotube/collections/fake.dart'; -import 'package:spotube/components/playbutton_view/playbutton_view.dart'; -import 'package:spotube/modules/album/album_card.dart'; -import 'package:spotube/modules/artist/artist_card.dart'; -import 'package:spotube/modules/playlist/playlist_card.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/provider/spotify/views/home_section.dart'; - -@RoutePage() -class HomeFeedSectionPage extends HookConsumerWidget { - static const name = "home_feed_section"; - - final String sectionUri; - const HomeFeedSectionPage({ - super.key, - @PathParam("feedId") required this.sectionUri, - }); - - @override - Widget build(BuildContext context, ref) { - final homeFeedSection = ref.watch(homeSectionViewProvider(sectionUri)); - final section = homeFeedSection.asData?.value ?? FakeData.feedSection; - final controller = useScrollController(); - final isArtist = section.items.every((item) => item.artist != null); - - return SafeArea( - bottom: false, - child: Skeletonizer( - enabled: homeFeedSection.isLoading, - child: Scaffold( - headers: [ - TitleBar( - title: Text(section.title ?? ""), - ) - ], - child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - child: CustomScrollView( - controller: controller, - slivers: [ - // if (isArtist) - // SliverGrid.builder( - // gridDelegate: - // const SliverGridDelegateWithMaxCrossAxisExtent( - // maxCrossAxisExtent: 200, - // mainAxisExtent: 250, - // crossAxisSpacing: 8, - // mainAxisSpacing: 8, - // ), - // itemCount: section.items.length, - // itemBuilder: (context, index) { - // final item = section.items[index]; - // return ArtistCard(item.artist!.asArtist); - // }, - // ) - // else - // PlaybuttonView( - // controller: controller, - // itemCount: section.items.length, - // hasMore: false, - // isLoading: false, - // onRequestMore: () => {}, - // listItemBuilder: (context, index) { - // final item = section.items[index]; - // if (item.album != null) { - // return AlbumCard.tile(item.album!.asAlbum); - // } - // if (item.playlist != null) { - // return PlaylistCard.tile(item.playlist!.asPlaylist); - // } - // return const SizedBox.shrink(); - // }, - // gridItemBuilder: (context, index) { - // final item = section.items[index]; - // if (item.album != null) { - // return AlbumCard(item.album!.asAlbum); - // } - // if (item.playlist != null) { - // return PlaylistCard(item.playlist!.asPlaylist); - // } - // return const SizedBox.shrink(); - // }, - // ), - const SliverToBoxAdapter( - child: SafeArea( - child: SizedBox(), - ), - ), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 1ef81027..53bb4fb2 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -12,7 +12,7 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/connect/connect_device.dart'; import 'package:spotube/modules/home/sections/featured.dart'; -import 'package:spotube/modules/home/sections/feed.dart'; +import 'package:spotube/modules/home/sections/sections.dart'; import 'package:spotube/modules/home/sections/friends.dart'; import 'package:spotube/modules/home/sections/genres/genres.dart'; import 'package:spotube/modules/home/sections/made_for_user.dart'; @@ -76,20 +76,19 @@ class HomePage extends HookConsumerWidget { else if (kIsMacOS) const SliverGap(10), const SliverGap(10), - SliverList.builder( - itemCount: 5, - itemBuilder: (context, index) { - return switch (index) { - 0 => const HomeGenresSection(), - 1 => const HomeRecentlyPlayedSection(), - 2 => const HomeFeaturedSection(), - 3 => const HomePageFriendsSection(), - _ => const HomeNewReleasesSection() - }; - }, - ), - const HomePageFeedSection(), - const SliverSafeArea(sliver: HomeMadeForUserSection()), + // SliverList.builder( + // itemCount: 5, + // itemBuilder: (context, index) { + // return switch (index) { + // 0 => const HomeGenresSection(), + // 1 => const HomeRecentlyPlayedSection(), + // 2 => const HomeFeaturedSection(), + // 3 => const HomePageFriendsSection(), + // _ => const HomeNewReleasesSection() + // }; + // }, + // ), + const HomePageBrowseSection(), ], ), )); diff --git a/lib/pages/home/sections/section_items.dart b/lib/pages/home/sections/section_items.dart new file mode 100644 index 00000000..89666d26 --- /dev/null +++ b/lib/pages/home/sections/section_items.dart @@ -0,0 +1,122 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/components/playbutton_view/playbutton_card.dart'; +import 'package:spotube/components/waypoint.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/modules/album/album_card.dart'; +import 'package:spotube/modules/artist/artist_card.dart'; +import 'package:spotube/modules/playlist/playlist_card.dart'; +import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/provider/metadata_plugin/browse/section_items.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; + +const _dummyPlaybuttonCard = PlaybuttonCard( + imageUrl: 'https://placehold.co/150x150.png', + isLoading: false, + isPlaying: false, + title: "Playbutton", + description: "A really cool playbutton", + isOwner: false, +); + +@RoutePage() +class HomeBrowseSectionItemsPage extends HookConsumerWidget { + static const name = "home_browse_section_items"; + + final String sectionId; + final SpotubeBrowseSectionObject section; + const HomeBrowseSectionItemsPage({ + super.key, + @PathParam("sectionId") required this.sectionId, + required this.section, + }); + + @override + Widget build(BuildContext context, ref) { + final scale = context.theme.scaling; + + final sectionItems = + ref.watch(metadataPluginBrowseSectionItemsProvider(sectionId)); + final sectionItemsNotifier = + ref.watch(metadataPluginBrowseSectionItemsProvider(sectionId).notifier); + final items = sectionItems.asData?.value.items ?? []; + final controller = useScrollController(); + + final isLoading = sectionItems.isLoading || sectionItems.isLoadingNextPage; + final itemCount = items.length; + final hasMore = sectionItems.asData?.value.hasMore ?? false; + + return SafeArea( + bottom: false, + child: Skeletonizer( + enabled: sectionItems.isLoading, + child: Scaffold( + headers: [ + TitleBar( + title: Text(section.title), + ) + ], + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 8.0), + child: CustomScrollView( + controller: controller, + slivers: [ + SliverGrid.builder( + itemCount: isLoading ? 6 : itemCount + 1, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 150 * scale, + mainAxisExtent: 225 * scale, + crossAxisSpacing: 12 * scale, + mainAxisSpacing: 12 * scale, + ), + itemBuilder: (context, index) { + if (isLoading) { + return const Skeletonizer( + enabled: true, + child: _dummyPlaybuttonCard, + ); + } + + if (index == itemCount) { + if (!hasMore) return const SizedBox.shrink(); + return Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: () async { + await sectionItemsNotifier.fetchMore(); + }, + child: const Skeletonizer( + enabled: true, + child: _dummyPlaybuttonCard, + ), + ); + } + + final item = items[index]; + return switch (item) { + SpotubeFullArtistObject() => ArtistCard(item), + SpotubeSimplePlaylistObject() => PlaylistCard(item), + SpotubeSimpleAlbumObject() => AlbumCard(item), + _ => throw Exception( + "Unsupported item type: ${item.runtimeType}", + ), + }; + }, + ), + const SliverToBoxAdapter( + child: SafeArea( + child: SizedBox(), + ), + ), + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/provider/metadata_plugin/browse/section_items.dart b/lib/provider/metadata_plugin/browse/section_items.dart new file mode 100644 index 00000000..542db00e --- /dev/null +++ b/lib/provider/metadata_plugin/browse/section_items.dart @@ -0,0 +1,32 @@ +import 'package:flutter_riverpod/flutter_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/utils/family_paginated.dart'; + +class MetadataPluginBrowseSectionItemsNotifier + extends FamilyPaginatedAsyncNotifier { + @override + Future> fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin).browse.sectionItems( + arg, + limit: limit, + offset: offset, + ); + } + + @override + build(arg) async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginBrowseSectionItemsProvider = AsyncNotifierProviderFamily< + MetadataPluginBrowseSectionItemsNotifier, + SpotubePaginationResponseObject, + String>( + () => MetadataPluginBrowseSectionItemsNotifier(), +); diff --git a/lib/provider/metadata_plugin/browse/sections.dart b/lib/provider/metadata_plugin/browse/sections.dart new file mode 100644 index 00000000..957daa86 --- /dev/null +++ b/lib/provider/metadata_plugin/browse/sections.dart @@ -0,0 +1,31 @@ +import 'package:flutter_riverpod/flutter_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/utils/paginated.dart'; + +class MetadataPluginBrowseSectionsNotifier + extends PaginatedAsyncNotifier> { + @override + Future>> + fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin).browse.sections( + limit: limit, + offset: offset, + ); + } + + @override + build() async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginBrowseSectionsProvider = AsyncNotifierProvider< + MetadataPluginBrowseSectionsNotifier, + SpotubePaginationResponseObject>>( + () => MetadataPluginBrowseSectionsNotifier(), +); diff --git a/lib/provider/metadata_plugin/utils/common.dart b/lib/provider/metadata_plugin/utils/common.dart index 2c9877a3..98a1f4e4 100644 --- a/lib/provider/metadata_plugin/utils/common.dart +++ b/lib/provider/metadata_plugin/utils/common.dart @@ -9,6 +9,10 @@ import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/services/metadata/endpoints/error.dart'; import 'package:spotube/services/metadata/metadata.dart'; +extension PaginationExtension on AsyncValue { + bool get isLoadingNextPage => this is AsyncData && this is AsyncLoadingNext; +} + mixin MetadataPluginMixin // ignore: invalid_use_of_internal_member on AsyncNotifierBase> { diff --git a/lib/services/metadata/endpoints/browse.dart b/lib/services/metadata/endpoints/browse.dart index e69de29b..c8105ad1 100644 --- a/lib/services/metadata/endpoints/browse.dart +++ b/lib/services/metadata/endpoints/browse.dart @@ -0,0 +1,87 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginBrowseEndpoint { + final Hetu hetu; + MetadataPluginBrowseEndpoint(this.hetu); + + HTInstance get hetuMetadataBrowse => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("browse") + as HTInstance; + + Future>> + sections({ + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataBrowse.invoke( + "sections", + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject< + SpotubeBrowseSectionObject>.fromJson( + raw.cast(), + (Map json) => SpotubeBrowseSectionObject.fromJson( + json.cast(), + (json) { + final isPlaylist = json["owner"] != null; + final isAlbum = json["artists"] != null; + if (isPlaylist) { + return SpotubeSimplePlaylistObject.fromJson( + json.cast(), + ); + } else if (isAlbum) { + return SpotubeSimpleAlbumObject.fromJson( + json.cast(), + ); + } else { + return SpotubeFullArtistObject.fromJson( + json.cast(), + ); + } + }, + ), + ); + } + + Future> sectionItems( + String id, { + int? offset, + int? limit, + }) async { + final raw = await hetuMetadataBrowse.invoke( + "sectionItems", + positionalArgs: [id], + namedArgs: { + "offset": offset, + "limit": limit, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (json) { + final isPlaylist = json["owner"] != null; + final isAlbum = json["artists"] != null; + if (isPlaylist) { + return SpotubeSimplePlaylistObject.fromJson( + json.cast(), + ); + } else if (isAlbum) { + return SpotubeSimpleAlbumObject.fromJson( + json.cast(), + ); + } else { + return SpotubeFullArtistObject.fromJson( + json.cast(), + ); + } + }, + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 38d35d23..034459a9 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -14,6 +14,7 @@ import 'package:spotube/services/metadata/apis/localstorage.dart'; import 'package:spotube/services/metadata/endpoints/album.dart'; import 'package:spotube/services/metadata/endpoints/artist.dart'; 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/user.dart'; @@ -78,6 +79,7 @@ class MetadataPlugin { late final MetadataPluginAlbumEndpoint album; late final MetadataPluginArtistEndpoint artist; + late final MetadataPluginBrowseEndpoint browse; late final MetadataPluginPlaylistEndpoint playlist; late final MetadataPluginUserEndpoint user; @@ -86,6 +88,7 @@ class MetadataPlugin { artist = MetadataPluginArtistEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu); + browse = MetadataPluginBrowseEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu); } From fcc05a42432fa61275a8d258fb313d84ee033e63 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 16 Jun 2025 09:52:02 +0600 Subject: [PATCH 19/60] refactor: search to use metadata plugin --- lib/models/metadata/artist.dart | 1 + lib/models/metadata/metadata.freezed.dart | 76 ++++++--- lib/models/metadata/metadata.g.dart | 7 +- lib/models/metadata/search.dart | 2 +- lib/pages/search/search.dart | 26 ++-- lib/pages/search/sections/albums.dart | 24 +-- lib/pages/search/sections/artists.dart | 16 +- lib/pages/search/sections/playlists.dart | 17 +- lib/provider/metadata_plugin/search/all.dart | 19 +++ lib/services/metadata/endpoints/search.dart | 156 +++++++++++++++++++ lib/services/metadata/metadata.dart | 3 + 11 files changed, 277 insertions(+), 70 deletions(-) create mode 100644 lib/provider/metadata_plugin/search/all.dart diff --git a/lib/models/metadata/artist.dart b/lib/models/metadata/artist.dart index 89c59082..24d8f55c 100644 --- a/lib/models/metadata/artist.dart +++ b/lib/models/metadata/artist.dart @@ -21,6 +21,7 @@ class SpotubeSimpleArtistObject with _$SpotubeSimpleArtistObject { required String id, required String name, required String externalUri, + List? images, }) = _SpotubeSimpleArtistObject; factory SpotubeSimpleArtistObject.fromJson(Map json) => diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 2bf50c6c..d01743a4 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -994,6 +994,7 @@ mixin _$SpotubeSimpleArtistObject { String get id => throw _privateConstructorUsedError; String get name => throw _privateConstructorUsedError; String get externalUri => throw _privateConstructorUsedError; + List? get images => throw _privateConstructorUsedError; /// Serializes this SpotubeSimpleArtistObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -1011,7 +1012,11 @@ abstract class $SpotubeSimpleArtistObjectCopyWith<$Res> { $Res Function(SpotubeSimpleArtistObject) then) = _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, SpotubeSimpleArtistObject>; @useResult - $Res call({String id, String name, String externalUri}); + $Res call( + {String id, + String name, + String externalUri, + List? images}); } /// @nodoc @@ -1033,6 +1038,7 @@ class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, Object? id = null, Object? name = null, Object? externalUri = null, + Object? images = freezed, }) { return _then(_value.copyWith( id: null == id @@ -1047,6 +1053,10 @@ class _$SpotubeSimpleArtistObjectCopyWithImpl<$Res, ? _value.externalUri : externalUri // ignore: cast_nullable_to_non_nullable as String, + images: freezed == images + ? _value.images + : images // ignore: cast_nullable_to_non_nullable + as List?, ) as $Val); } } @@ -1060,7 +1070,11 @@ abstract class _$$SpotubeSimpleArtistObjectImplCopyWith<$Res> __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res>; @override @useResult - $Res call({String id, String name, String externalUri}); + $Res call( + {String id, + String name, + String externalUri, + List? images}); } /// @nodoc @@ -1081,6 +1095,7 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res> Object? id = null, Object? name = null, Object? externalUri = null, + Object? images = freezed, }) { return _then(_$SpotubeSimpleArtistObjectImpl( id: null == id @@ -1095,6 +1110,10 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res> ? _value.externalUri : externalUri // ignore: cast_nullable_to_non_nullable as String, + images: freezed == images + ? _value._images + : images // ignore: cast_nullable_to_non_nullable + as List?, )); } } @@ -1103,7 +1122,11 @@ class __$$SpotubeSimpleArtistObjectImplCopyWithImpl<$Res> @JsonSerializable() class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { _$SpotubeSimpleArtistObjectImpl( - {required this.id, required this.name, required this.externalUri}); + {required this.id, + required this.name, + required this.externalUri, + final List? images}) + : _images = images; factory _$SpotubeSimpleArtistObjectImpl.fromJson(Map json) => _$$SpotubeSimpleArtistObjectImplFromJson(json); @@ -1114,10 +1137,19 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { final String name; @override final String externalUri; + final List? _images; + @override + List? get images { + final value = _images; + if (value == null) return null; + if (_images is EqualUnmodifiableListView) return _images; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(value); + } @override String toString() { - return 'SpotubeSimpleArtistObject(id: $id, name: $name, externalUri: $externalUri)'; + return 'SpotubeSimpleArtistObject(id: $id, name: $name, externalUri: $externalUri, images: $images)'; } @override @@ -1128,12 +1160,14 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && (identical(other.externalUri, externalUri) || - other.externalUri == externalUri)); + other.externalUri == externalUri) && + const DeepCollectionEquality().equals(other._images, _images)); } @JsonKey(includeFromJson: false, includeToJson: false) @override - int get hashCode => Object.hash(runtimeType, id, name, externalUri); + int get hashCode => Object.hash(runtimeType, id, name, externalUri, + const DeepCollectionEquality().hash(_images)); /// Create a copy of SpotubeSimpleArtistObject /// with the given fields replaced by the non-null parameter values. @@ -1154,9 +1188,11 @@ class _$SpotubeSimpleArtistObjectImpl implements _SpotubeSimpleArtistObject { abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject { factory _SpotubeSimpleArtistObject( - {required final String id, - required final String name, - required final String externalUri}) = _$SpotubeSimpleArtistObjectImpl; + {required final String id, + required final String name, + required final String externalUri, + final List? images}) = + _$SpotubeSimpleArtistObjectImpl; factory _SpotubeSimpleArtistObject.fromJson(Map json) = _$SpotubeSimpleArtistObjectImpl.fromJson; @@ -1167,6 +1203,8 @@ abstract class _SpotubeSimpleArtistObject implements SpotubeSimpleArtistObject { String get name; @override String get externalUri; + @override + List? get images; /// Create a copy of SpotubeSimpleArtistObject /// with the given fields replaced by the non-null parameter values. @@ -2529,7 +2567,7 @@ SpotubeSearchResponseObject _$SpotubeSearchResponseObjectFromJson( mixin _$SpotubeSearchResponseObject { List get albums => throw _privateConstructorUsedError; - List get artists => + List get artists => throw _privateConstructorUsedError; List get playlists => throw _privateConstructorUsedError; @@ -2556,7 +2594,7 @@ abstract class $SpotubeSearchResponseObjectCopyWith<$Res> { @useResult $Res call( {List albums, - List artists, + List artists, List playlists, List tracks}); } @@ -2590,7 +2628,7 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, artists: null == artists ? _value.artists : artists // ignore: cast_nullable_to_non_nullable - as List, + as List, playlists: null == playlists ? _value.playlists : playlists // ignore: cast_nullable_to_non_nullable @@ -2614,7 +2652,7 @@ abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res> @useResult $Res call( {List albums, - List artists, + List artists, List playlists, List tracks}); } @@ -2647,7 +2685,7 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> artists: null == artists ? _value._artists : artists // ignore: cast_nullable_to_non_nullable - as List, + as List, playlists: null == playlists ? _value._playlists : playlists // ignore: cast_nullable_to_non_nullable @@ -2666,7 +2704,7 @@ class _$SpotubeSearchResponseObjectImpl implements _SpotubeSearchResponseObject { _$SpotubeSearchResponseObjectImpl( {required final List albums, - required final List artists, + required final List artists, required final List playlists, required final List tracks}) : _albums = albums, @@ -2686,9 +2724,9 @@ class _$SpotubeSearchResponseObjectImpl return EqualUnmodifiableListView(_albums); } - final List _artists; + final List _artists; @override - List get artists { + List get artists { if (_artists is EqualUnmodifiableListView) return _artists; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_artists); @@ -2757,7 +2795,7 @@ abstract class _SpotubeSearchResponseObject implements SpotubeSearchResponseObject { factory _SpotubeSearchResponseObject( {required final List albums, - required final List artists, + required final List artists, required final List playlists, required final List tracks}) = _$SpotubeSearchResponseObjectImpl; @@ -2768,7 +2806,7 @@ abstract class _SpotubeSearchResponseObject @override List get albums; @override - List get artists; + List get artists; @override List get playlists; @override diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 52001504..ff263819 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -113,6 +113,10 @@ _$SpotubeSimpleArtistObjectImpl _$$SpotubeSimpleArtistObjectImplFromJson( id: json['id'] as String, name: json['name'] as String, externalUri: json['externalUri'] as String, + images: (json['images'] as List?) + ?.map((e) => + SpotubeImageObject.fromJson(Map.from(e as Map))) + .toList(), ); Map _$$SpotubeSimpleArtistObjectImplToJson( @@ -121,6 +125,7 @@ Map _$$SpotubeSimpleArtistObjectImplToJson( 'id': instance.id, 'name': instance.name, 'externalUri': instance.externalUri, + 'images': instance.images?.map((e) => e.toJson()).toList(), }; _$SpotubeBrowseSectionObjectImpl @@ -260,7 +265,7 @@ _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson( Map.from(e as Map))) .toList(), artists: (json['artists'] as List) - .map((e) => SpotubeSimpleArtistObject.fromJson( + .map((e) => SpotubeFullArtistObject.fromJson( Map.from(e as Map))) .toList(), playlists: (json['playlists'] as List) diff --git a/lib/models/metadata/search.dart b/lib/models/metadata/search.dart index e655d892..4918c898 100644 --- a/lib/models/metadata/search.dart +++ b/lib/models/metadata/search.dart @@ -4,7 +4,7 @@ part of 'metadata.dart'; class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject { factory SpotubeSearchResponseObject({ required List albums, - required List artists, + required List artists, required List playlists, required List tracks, }) = _SpotubeSearchResponseObject; diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 25fb046a..19043f42 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -2,7 +2,6 @@ import 'package:flutter/services.dart'; import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -18,8 +17,8 @@ import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dar import 'package:spotube/pages/search/sections/albums.dart'; import 'package:spotube/pages/search/sections/artists.dart'; import 'package:spotube/pages/search/sections/playlists.dart'; -import 'package:spotube/pages/search/sections/tracks.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:auto_route/auto_route.dart'; @@ -39,17 +38,11 @@ class SearchPage extends HookConsumerWidget { final controller = useShadcnTextEditingController(); final focusNode = useFocusNode(); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final searchTerm = ref.watch(searchTermStateProvider); - final searchTrack = ref.watch(searchProvider(SearchType.track)); - final searchAlbum = ref.watch(searchProvider(SearchType.album)); - final searchPlaylist = ref.watch(searchProvider(SearchType.playlist)); - final searchArtist = ref.watch(searchProvider(SearchType.artist)); - - final queries = [searchTrack, searchAlbum, searchPlaylist, searchArtist]; - - final isFetching = queries.every((s) => s.isLoading); + final searchSnapshot = + ref.watch(metadataPluginSearchAllProvider(searchTerm)); useEffect(() { controller.text = searchTerm; @@ -82,7 +75,7 @@ class SearchPage extends HookConsumerWidget { if (kTitlebarVisible) const TitleBar(automaticallyImplyLeading: false, height: 30) ], - child: auth.asData?.value == null + child: authenticated.asData?.value != true ? const AnonymousFallback() : Column( children: [ @@ -174,7 +167,10 @@ class SearchPage extends HookConsumerWidget { Expanded( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: switch ((searchTerm.isEmpty, isFetching)) { + child: switch (( + searchTerm.isEmpty, + searchSnapshot.isLoading + )) { (true, false) => Column( children: [ SizedBox( @@ -228,7 +224,7 @@ class SearchPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - SearchTracksSection(), + // SearchTracksSection(), SearchPlaylistsSection(), Gap(20), SearchArtistsSection(), diff --git a/lib/pages/search/sections/albums.dart b/lib/pages/search/sections/albums.dart index 105c23d5..249e0e6d 100644 --- a/lib/pages/search/sections/albums.dart +++ b/lib/pages/search/sections/albums.dart @@ -1,11 +1,9 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/extensions/album_simple.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class SearchAlbumsSection extends HookConsumerWidget { @@ -15,23 +13,15 @@ class SearchAlbumsSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final query = ref.watch(searchProvider(SearchType.album)); - final notifier = ref.watch(searchProvider(SearchType.album).notifier); - final albums = useMemoized( - () => - query.asData?.value.items - .cast() - .map((e) => e.toAlbum()) - .toList() ?? - [], - [query.asData?.value], - ); + final searchTerm = ref.watch(searchTermStateProvider); + final search = ref.watch(metadataPluginSearchAllProvider(searchTerm)); + final albums = search.asData?.value.albums ?? []; return HorizontalPlaybuttonCardView( - isLoadingNextPage: query.isLoadingNextPage, - hasNextPage: query.asData?.value.hasMore == true, + isLoadingNextPage: false, + hasNextPage: false, items: albums, - onFetchMore: notifier.fetchMore, + onFetchMore: () {}, title: Text(context.l10n.albums), ); } diff --git a/lib/pages/search/sections/artists.dart b/lib/pages/search/sections/artists.dart index 9a94b3c1..ac009cf4 100644 --- a/lib/pages/search/sections/artists.dart +++ b/lib/pages/search/sections/artists.dart @@ -1,9 +1,9 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class SearchArtistsSection extends HookConsumerWidget { @@ -13,16 +13,16 @@ class SearchArtistsSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final query = ref.watch(searchProvider(SearchType.artist)); - final notifier = ref.watch(searchProvider(SearchType.artist).notifier); + final searchTerm = ref.watch(searchTermStateProvider); + final search = ref.watch(metadataPluginSearchAllProvider(searchTerm)); - final artists = query.asData?.value.items.cast() ?? []; + final artists = search.asData?.value.artists ?? []; - return HorizontalPlaybuttonCardView( - isLoadingNextPage: query.isLoadingNextPage, - hasNextPage: query.asData?.value.hasMore == true, + return HorizontalPlaybuttonCardView( + isLoadingNextPage: false, + hasNextPage: false, items: artists, - onFetchMore: notifier.fetchMore, + onFetchMore: () {}, title: Text(context.l10n.artists), ); } diff --git a/lib/pages/search/sections/playlists.dart b/lib/pages/search/sections/playlists.dart index 17bf4849..2c387f28 100644 --- a/lib/pages/search/sections/playlists.dart +++ b/lib/pages/search/sections/playlists.dart @@ -1,8 +1,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/provider/spotify/spotify.dart'; class SearchPlaylistsSection extends HookConsumerWidget { @@ -12,17 +12,16 @@ class SearchPlaylistsSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final playlistsQuery = ref.watch(searchProvider(SearchType.playlist)); - final playlistsQueryNotifier = - ref.watch(searchProvider(SearchType.playlist).notifier); - final playlists = - playlistsQuery.asData?.value.items.cast() ?? []; + final searchTerm = ref.watch(searchTermStateProvider); + final playlistsQuery = + ref.watch(metadataPluginSearchAllProvider(searchTerm)); + final playlists = playlistsQuery.asData?.value.playlists ?? []; return HorizontalPlaybuttonCardView( - isLoadingNextPage: playlistsQuery.isLoadingNextPage, - hasNextPage: playlistsQuery.asData?.value.hasMore == true, + isLoadingNextPage: false, + hasNextPage: false, items: playlists, - onFetchMore: playlistsQueryNotifier.fetchMore, + onFetchMore: () {}, title: Text(context.l10n.playlists), ); } diff --git a/lib/provider/metadata_plugin/search/all.dart b/lib/provider/metadata_plugin/search/all.dart new file mode 100644 index 00000000..53c79f7c --- /dev/null +++ b/lib/provider/metadata_plugin/search/all.dart @@ -0,0 +1,19 @@ +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/services/metadata/endpoints/error.dart'; + +final metadataPluginSearchAllProvider = + FutureProvider.autoDispose.family( + (ref, query) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No default metadata plugin found", + ); + } + + return metadataPlugin.search.all(query); + }, +); diff --git a/lib/services/metadata/endpoints/search.dart b/lib/services/metadata/endpoints/search.dart index e69de29b..070628c2 100644 --- a/lib/services/metadata/endpoints/search.dart +++ b/lib/services/metadata/endpoints/search.dart @@ -0,0 +1,156 @@ +import 'package:hetu_script/hetu_script.dart'; +import 'package:hetu_script/values.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +class MetadataPluginSearchEndpoint { + final Hetu hetu; + MetadataPluginSearchEndpoint(this.hetu); + + HTInstance get hetuMetadataSearch => + (hetu.fetch("metadataPlugin") as HTInstance).memberGet("search") + as HTInstance; + + Future all(String query) async { + if (query.isEmpty) { + return SpotubeSearchResponseObject( + albums: [], + artists: [], + playlists: [], + tracks: [], + ); + } + + final raw = await hetuMetadataSearch.invoke( + "all", + positionalArgs: [query], + ) as Map; + + return SpotubeSearchResponseObject.fromJson(raw.cast()); + } + + Future> albums( + String query, { + int? limit, + int? offset, + }) async { + if (query.isEmpty) { + return SpotubePaginationResponseObject( + items: [], + total: 0, + limit: limit ?? 20, + hasMore: false, + nextOffset: null, + ); + } + + final raw = await hetuMetadataSearch.invoke( + "albums", + positionalArgs: [query], + namedArgs: { + "limit": limit, + "offset": offset, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (json) => SpotubeSimpleAlbumObject.fromJson(json.cast()), + ); + } + + Future> artists( + String query, { + int? limit, + int? offset, + }) async { + if (query.isEmpty) { + return SpotubePaginationResponseObject( + items: [], + total: 0, + limit: limit ?? 20, + hasMore: false, + nextOffset: null, + ); + } + + final raw = await hetuMetadataSearch.invoke( + "artists", + positionalArgs: [query], + namedArgs: { + "limit": limit, + "offset": offset, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (json) => SpotubeFullArtistObject.fromJson( + json.cast(), + ), + ); + } + + Future> + playlists( + String query, { + int? limit, + int? offset, + }) async { + if (query.isEmpty) { + return SpotubePaginationResponseObject( + items: [], + total: 0, + limit: limit ?? 20, + hasMore: false, + nextOffset: null, + ); + } + + final raw = await hetuMetadataSearch.invoke( + "playlists", + positionalArgs: [query], + namedArgs: { + "limit": limit, + "offset": offset, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject< + SpotubeSimplePlaylistObject>.fromJson( + raw.cast(), + (json) => SpotubeSimplePlaylistObject.fromJson( + json.cast(), + ), + ); + } + + Future> tracks( + String query, { + int? limit, + int? offset, + }) async { + if (query.isEmpty) { + return SpotubePaginationResponseObject( + items: [], + total: 0, + limit: limit ?? 20, + hasMore: false, + nextOffset: null, + ); + } + + final raw = await hetuMetadataSearch.invoke( + "tracks", + positionalArgs: [query], + namedArgs: { + "limit": limit, + "offset": offset, + }..removeWhere((key, value) => value == null), + ) as Map; + + return SpotubePaginationResponseObject.fromJson( + raw.cast(), + (json) => SpotubeSimpleTrackObject.fromJson(json.cast()), + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 034459a9..1d1eaf7d 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -16,6 +16,7 @@ import 'package:spotube/services/metadata/endpoints/artist.dart'; 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/user.dart'; const defaultMetadataLimit = "20"; @@ -80,6 +81,7 @@ class MetadataPlugin { late final MetadataPluginAlbumEndpoint album; late final MetadataPluginArtistEndpoint artist; late final MetadataPluginBrowseEndpoint browse; + late final MetadataPluginSearchEndpoint search; late final MetadataPluginPlaylistEndpoint playlist; late final MetadataPluginUserEndpoint user; @@ -89,6 +91,7 @@ class MetadataPlugin { artist = MetadataPluginArtistEndpoint(hetu); album = MetadataPluginAlbumEndpoint(hetu); browse = MetadataPluginBrowseEndpoint(hetu); + search = MetadataPluginSearchEndpoint(hetu); playlist = MetadataPluginPlaylistEndpoint(hetu); user = MetadataPluginUserEndpoint(hetu); } From b979a6ede96384a543bc40532d8bf77ab306f9f8 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Mon, 16 Jun 2025 14:09:20 +0600 Subject: [PATCH 20/60] feat: add track endpoint for metadata service --- lib/collections/fake.dart | 10 + .../heart_button/use_track_toggle_like.dart | 35 +- lib/models/metadata/metadata.freezed.dart | 567 ++++++++++++------ lib/models/metadata/metadata.g.dart | 4 + lib/models/metadata/track.dart | 24 +- .../metadata_plugin/library/tracks.dart | 71 +++ lib/services/metadata/endpoints/track.dart | 29 + lib/services/metadata/metadata.dart | 3 + 8 files changed, 540 insertions(+), 203 deletions(-) create mode 100644 lib/provider/metadata_plugin/library/tracks.dart diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart index 849b4d17..09bb8087 100644 --- a/lib/collections/fake.dart +++ b/lib/collections/fake.dart @@ -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" diff --git a/lib/components/heart_button/use_track_toggle_like.dart b/lib/components/heart_button/use_track_toggle_like.dart index ba5cbee1..349a4ab2 100644 --- a/lib/components/heart_button/use_track_toggle_like.dart +++ b/lib/components/heart_button/use_track_toggle_like.dart @@ -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 Function(Track track) toggleTrackLike, + Future 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]); } }, ); diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index d01743a4..d1fa4f45 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -2820,38 +2820,134 @@ abstract class _SpotubeSearchResponseObject get copyWith => throw _privateConstructorUsedError; } -SpotubeFullTrackObject _$SpotubeFullTrackObjectFromJson( - Map json) { - return _SpotubeFullTrackObject.fromJson(json); +SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map 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 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({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit) + full, + required TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album) + simple, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult? Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeFullTrackObject value) full, + required TResult Function(SpotubeSimpleTrackObject value) simple, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeFullTrackObject value)? full, + TResult? Function(SpotubeSimpleTrackObject value)? simple, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + 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 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 get copyWith => + $SpotubeTrackObjectCopyWith 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 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, 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 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({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit) + full, + required TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album) + simple, + }) { + return full( + id, name, externalUri, artists, album, durationMs, isrc, explicit); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult? Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + }) { + return full?.call( + id, name, externalUri, artists, album, durationMs, isrc, explicit); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List 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({ + required TResult Function(SpotubeFullTrackObject value) full, + required TResult Function(SpotubeSimpleTrackObject value) simple, + }) { + return full(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeFullTrackObject value)? full, + TResult? Function(SpotubeSimpleTrackObject value)? simple, + }) { + return full?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeFullTrackObject value)? full, + TResult Function(SpotubeSimpleTrackObject value)? simple, + required TResult orElse(), + }) { + if (full != null) { + return full(this); + } + return orElse(); + } + @override Map 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 json) = + factory SpotubeFullTrackObject.fromJson(Map 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 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 get artists => - throw _privateConstructorUsedError; - SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError; - - /// Serializes this SpotubeSimpleTrackObject to a JSON map. - Map 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 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 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, - 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 artists = const [], - this.album}) - : _artists = artists; + this.album, + final String? $type}) + : _artists = artists, + $type = $type ?? 'simple'; factory _$SpotubeSimpleTrackObjectImpl.fromJson(Map 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({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit) + full, + required TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album) + simple, + }) { + return simple(id, name, externalUri, durationMs, explicit, artists, album); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult? Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + }) { + return simple?.call( + id, name, externalUri, durationMs, explicit, artists, album); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List 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({ + required TResult Function(SpotubeFullTrackObject value) full, + required TResult Function(SpotubeSimpleTrackObject value) simple, + }) { + return simple(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeFullTrackObject value)? full, + TResult? Function(SpotubeSimpleTrackObject value)? simple, + }) { + return simple?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeFullTrackObject value)? full, + TResult Function(SpotubeSimpleTrackObject value)? simple, + required TResult orElse(), + }) { + if (simple != null) { + return simple(this); + } + return orElse(); + } + @override Map 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 artists, final SpotubeSimpleAlbumObject? album}) = _$SpotubeSimpleTrackObjectImpl; - factory _SpotubeSimpleTrackObject.fromJson(Map json) = + factory SpotubeSimpleTrackObject.fromJson(Map 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) diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index ff263819..887df6d2 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -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 _$$SpotubeFullTrackObjectImplToJson( @@ -315,6 +316,7 @@ Map _$$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.from(json['album'] as Map)), + $type: json['runtimeType'] as String?, ); Map _$$SpotubeSimpleTrackObjectImplToJson( @@ -346,6 +349,7 @@ Map _$$SpotubeSimpleTrackObjectImplToJson( 'explicit': instance.explicit, 'artists': instance.artists.map((e) => e.toJson()).toList(), 'album': instance.album?.toJson(), + 'runtimeType': instance.$type, }; _$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) => diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index f56344f3..eb82f2d8 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -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 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 artists, SpotubeSimpleAlbumObject? album, - }) = _SpotubeSimpleTrackObject; + }) = SpotubeSimpleTrackObject; - factory SpotubeSimpleTrackObject.fromJson(Map json) => - _$SpotubeSimpleTrackObjectFromJson(json); + factory SpotubeTrackObject.fromJson(Map json) => + _$SpotubeTrackObjectFromJson( + json.containsKey("isrc") + ? {...json, "runtimeType": "full"} + : {...json, "runtimeType": "simple"}, + ); } diff --git a/lib/provider/metadata_plugin/library/tracks.dart b/lib/provider/metadata_plugin/library/tracks.dart new file mode 100644 index 00000000..9645deda --- /dev/null +++ b/lib/provider/metadata_plugin/library/tracks.dart @@ -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 { + 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 addFavorite(List tracks) async { + await (await metadataPlugin).track.save(tracks.map((e) => e.id).toList()); + + for (final track in tracks) { + ref.invalidate(metadataPluginIsSavedTrackProvider(track.id)); + } + } + + Future removeFavorite(List 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>( + () => MetadataPluginSavedTracksNotifier(), +); + +final metadataPluginIsSavedTrackProvider = + FutureProvider.autoDispose.family( + (ref, trackId) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + return metadataPlugin!.user + .isSavedTracks([trackId]).then((value) => value.first); + }, +); diff --git a/lib/services/metadata/endpoints/track.dart b/lib/services/metadata/endpoints/track.dart index e69de29b..ce509a82 100644 --- a/lib/services/metadata/endpoints/track.dart +++ b/lib/services/metadata/endpoints/track.dart @@ -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 getTrack(String id) async { + final raw = + await hetuMetadataTrack.invoke("getTrack", positionalArgs: [id]) as Map; + + return SpotubeFullTrackObject.fromJson( + raw.cast(), + ); + } + + Future save(List ids) async { + await hetuMetadataTrack.invoke("save", positionalArgs: [ids]); + } + + Future unsave(List ids) async { + await hetuMetadataTrack.invoke("unsave", positionalArgs: [ids]); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 1d1eaf7d..98dad35a 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -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); } } From 4e6db8b9e1f219c24d4d0185ccd0b0478381c519 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 18 Jun 2025 21:30:32 +0600 Subject: [PATCH 21/60] refactor: move from Track to SpotubeTrackObject and use TrackSources object for providers --- lib/extensions/track.dart | 10 +- .../configurators/use_endless_playback.dart | 135 +- lib/models/connect/connect.dart | 2 +- lib/models/connect/connect.freezed.dart | 206 ++- lib/models/connect/connect.g.dart | 14 +- lib/models/connect/load.dart | 12 +- lib/models/connect/ws_event.dart | 9 +- lib/models/current_playlist.dart | 75 - lib/models/database/database.dart | 4 +- lib/models/database/database.g.dart | 1402 ++--------------- .../database/tables/audio_player_state.dart | 36 +- .../database/typeconverters/map_list.dart | 20 + lib/models/metadata/metadata.dart | 5 + lib/models/metadata/metadata.freezed.dart | 455 +++++- lib/models/metadata/metadata.g.dart | 31 + lib/models/metadata/track.dart | 46 +- lib/models/playback/track_sources.dart | 94 ++ .../playback/track_sources.freezed.dart | 776 +++++++++ lib/models/playback/track_sources.g.dart | 110 ++ lib/modules/player/player.dart | 4 +- lib/modules/player/sibling_tracks_sheet.dart | 6 +- lib/provider/audio_player/audio_player.dart | 181 +-- .../audio_player/audio_player_streams.dart | 49 +- .../audio_player/querying_track_info.dart | 30 +- lib/provider/audio_player/state.dart | 130 +- lib/provider/audio_player/state.freezed.dart | 297 ++++ lib/provider/audio_player/state.g.dart | 40 + lib/provider/blacklist_provider.dart | 25 +- lib/provider/connect/connect.dart | 7 +- lib/provider/discord_provider.dart | 14 +- lib/provider/download_manager_provider.dart | 101 +- lib/provider/glance/glance.dart | 8 +- lib/provider/history/history.dart | 22 +- lib/provider/scrobbler/scrobbler.dart | 30 +- lib/provider/server/active_sourced_track.dart | 47 - lib/provider/server/active_track_sources.dart | 42 + lib/provider/server/routes/connect.dart | 12 +- lib/provider/server/routes/playback.dart | 62 +- lib/provider/server/sourced_track.dart | 49 - lib/provider/server/track_sources.dart | 45 + lib/provider/skip_segments/skip_segments.dart | 19 +- lib/services/audio_player/audio_player.dart | 55 +- .../audio_services/audio_services.dart | 16 +- .../audio_services/windows_audio_service.dart | 12 +- lib/services/sourced_track/enums.dart | 8 +- lib/services/sourced_track/exceptions.dart | 6 +- .../sourced_track/models/source_info.dart | 33 - .../sourced_track/models/source_info.g.dart | 30 - .../sourced_track/models/source_map.dart | 58 - .../sourced_track/models/source_map.g.dart | 36 - lib/services/sourced_track/sourced_track.dart | 140 +- .../sourced_track/sources/invidious.dart | 167 +- .../sourced_track/sources/jiosaavn.dart | 130 +- lib/services/sourced_track/sources/piped.dart | 167 +- .../sourced_track/sources/youtube.dart | 199 ++- lib/utils/service_utils.dart | 15 +- 56 files changed, 3054 insertions(+), 2680 deletions(-) delete mode 100644 lib/models/current_playlist.dart create mode 100644 lib/models/database/typeconverters/map_list.dart create mode 100644 lib/models/playback/track_sources.dart create mode 100644 lib/models/playback/track_sources.freezed.dart create mode 100644 lib/models/playback/track_sources.g.dart create mode 100644 lib/provider/audio_player/state.freezed.dart create mode 100644 lib/provider/audio_player/state.g.dart delete mode 100644 lib/provider/server/active_sourced_track.dart create mode 100644 lib/provider/server/active_track_sources.dart delete mode 100644 lib/provider/server/sourced_track.dart create mode 100644 lib/provider/server/track_sources.dart delete mode 100644 lib/services/sourced_track/models/source_info.dart delete mode 100644 lib/services/sourced_track/models/source_info.g.dart delete mode 100644 lib/services/sourced_track/models/source_map.dart delete mode 100644 lib/services/sourced_track/models/source_map.g.dart diff --git a/lib/extensions/track.dart b/lib/extensions/track.dart index 92d8b0da..bfe1f639 100644 --- a/lib/extensions/track.dart +++ b/lib/extensions/track.dart @@ -73,8 +73,8 @@ extension IterableTrackSimpleExtensions on Iterable { Future> asTracks(AlbumSimple album, ref) async { try { final spotify = ref.read(spotifyProvider); - final tracks = await spotify.invoke( - (api) => api.tracks.list(map((trackSimple) => trackSimple.id!).toList())); + final tracks = await spotify.invoke((api) => + api.tracks.list(map((trackSimple) => trackSimple.id!).toList())); return tracks.toList(); } catch (e, stack) { // Ignore errors and create the track locally @@ -105,9 +105,3 @@ extension IterableTrackSimpleExtensions on Iterable { } } } - -extension TracksToMediaExtension on Iterable { - List asMediaList() { - return map((track) => SpotubeMedia(track)).toList(); - } -} diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index b86a4865..96628442 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -1,3 +1,4 @@ +import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; @@ -8,83 +9,85 @@ import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; +// TODO: Implement endless playback functionality void useEndlessPlayback(WidgetRef ref) { - final auth = ref.watch(authenticationProvider); - final playback = ref.watch(audioPlayerProvider.notifier); - final playlist = ref.watch(audioPlayerProvider.select((s) => s.playlist)); - final spotify = ref.watch(spotifyProvider); - final endlessPlayback = - ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback)); + // final auth = ref.watch(authenticationProvider); + // final playback = ref.watch(audioPlayerProvider.notifier); + // final audioPlayerState = ref.watch(audioPlayerProvider); + // final spotify = ref.watch(spotifyProvider); + // final endlessPlayback = + // ref.watch(userPreferencesProvider.select((s) => s.endlessPlayback)); - useEffect( - () { - if (!endlessPlayback || auth.asData?.value == null) return null; + // useEffect( + // () { + // if (!endlessPlayback || auth.asData?.value == null) return null; - void listener(int index) async { - try { - final playlist = ref.read(audioPlayerProvider); - if (index != playlist.tracks.length - 1) return; + // void listener(int index) async { + // try { + // final playlist = ref.read(audioPlayerProvider); + // if (index != playlist.tracks.length - 1) return; - final track = playlist.tracks.last; + // final track = playlist.tracks.last; - final query = "${track.name} Radio"; - final pages = await spotify.invoke((api) => - api.search.get(query, types: [SearchType.playlist]).first()); + // final query = "${track.name} Radio"; + // final pages = await spotify.invoke((api) => + // api.search.get(query, types: [SearchType.playlist]).first()); - final radios = pages - .expand((e) => e.items?.toList() ?? []) - .toList() - .cast(); + // final radios = pages + // .expand((e) => e.items?.toList() ?? []) + // .toList() + // .cast(); - final artists = track.artists!.map((e) => e.name); + // final artists = track.artists.map((e) => e.name); - final radio = radios.firstWhere( - (e) { - final validPlaylists = - artists.where((a) => e.description!.contains(a!)); - return e.name == "${track.name} Radio" && - (validPlaylists.length >= 2 || - validPlaylists.length == artists.length) && - e.owner?.displayName != "Spotify"; - }, - orElse: () => radios.first, - ); + // final radio = radios.firstWhere( + // (e) { + // final validPlaylists = + // artists.where((a) => e.description!.contains(a)); + // return e.name == "${track.name} Radio" && + // (validPlaylists.length >= 2 || + // validPlaylists.length == artists.length) && + // e.owner?.displayName != "Spotify"; + // }, + // orElse: () => radios.first, + // ); - final tracks = await spotify.invoke( - (api) => api.playlists.getTracksByPlaylistId(radio.id!).all()); + // final tracks = + // ref.read(metadataPluginPlaylistTracksProvider(radio.id!)); - await playback.addTracks( - tracks.toList() - ..removeWhere((e) { - final playlist = ref.read(audioPlayerProvider); - final isDuplicate = playlist.tracks.any((t) => t.id == e.id); - return e.id == track.id || isDuplicate; - }), - ); - } catch (e, stack) { - AppLogger.reportError(e, stack); - } - } + // await playback.addTracks( + // tracks.toList() + // ..removeWhere((e) { + // final playlist = ref.read(audioPlayerProvider); + // final isDuplicate = playlist.tracks.any((t) => t.id == e.id); + // return e.id == track.id || isDuplicate; + // }), + // ); + // } catch (e, stack) { + // AppLogger.reportError(e, stack); + // } + // } - // Sometimes user can change settings for which the currentIndexChanged - // might not be called. So we need to check if the current track is the - // last track and if it is then we need to call the listener manually. - if (playlist.index == playlist.medias.length - 1 && - audioPlayer.isPlaying) { - listener(playlist.index); - } + // // Sometimes user can change settings for which the currentIndexChanged + // // might not be called. So we need to check if the current track is the + // // last track and if it is then we need to call the listener manually. + // if (audioPlayerState.currentIndex == audioPlayerState.tracks.length - 1 && + // audioPlayer.isPlaying) { + // listener(audioPlayerState.currentIndex); + // } - final subscription = - audioPlayer.currentIndexChangedStream.listen(listener); + // final subscription = + // audioPlayer.currentIndexChangedStream.listen(listener); - return subscription.cancel; - }, - [ - spotify, - playback, - playlist.medias, - endlessPlayback, - auth, - ], - ); + // return subscription.cancel; + // }, + // [ + // spotify, + // playback, + // audioPlayerState.tracks, + // audioPlayerState.currentIndex, + // endlessPlayback, + // auth, + // ], + // ); } diff --git a/lib/models/connect/connect.dart b/lib/models/connect/connect.dart index a70520ad..11370dcb 100644 --- a/lib/models/connect/connect.dart +++ b/lib/models/connect/connect.dart @@ -5,7 +5,7 @@ import 'dart:convert'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:media_kit/media_kit.dart' hide Track; -import 'package:spotify/spotify.dart' hide Playlist; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/state.dart'; part 'connect.freezed.dart'; diff --git a/lib/models/connect/connect.freezed.dart b/lib/models/connect/connect.freezed.dart index 9103dd2b..ead14cf8 100644 --- a/lib/models/connect/connect.freezed.dart +++ b/lib/models/connect/connect.freezed.dart @@ -33,49 +33,36 @@ WebSocketLoadEventData _$WebSocketLoadEventDataFromJson( /// @nodoc mixin _$WebSocketLoadEventData { - @JsonKey(name: 'tracks', toJson: _tracksJson) - List get tracks => throw _privateConstructorUsedError; + List get tracks => throw _privateConstructorUsedError; Object? get collection => throw _privateConstructorUsedError; int? get initialIndex => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex) playlist, - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex) album, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, required TResult orElse(), }) => @@ -116,9 +103,7 @@ abstract class $WebSocketLoadEventDataCopyWith<$Res> { $Res Function(WebSocketLoadEventData) then) = _$WebSocketLoadEventDataCopyWithImpl<$Res, WebSocketLoadEventData>; @useResult - $Res call( - {@JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - int? initialIndex}); + $Res call({List tracks, int? initialIndex}); } /// @nodoc @@ -144,7 +129,7 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res, tracks: null == tracks ? _value.tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, initialIndex: freezed == initialIndex ? _value.initialIndex : initialIndex // ignore: cast_nullable_to_non_nullable @@ -163,9 +148,11 @@ abstract class _$$WebSocketLoadEventDataPlaylistImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, + {List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex}); + + $SpotubeSimplePlaylistObjectCopyWith<$Res>? get collection; } /// @nodoc @@ -191,17 +178,32 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> tracks: null == tracks ? _value._tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, collection: freezed == collection ? _value.collection : collection // ignore: cast_nullable_to_non_nullable - as PlaylistSimple?, + as SpotubeSimplePlaylistObject?, initialIndex: freezed == initialIndex ? _value.initialIndex : initialIndex // ignore: cast_nullable_to_non_nullable as int?, )); } + + /// Create a copy of WebSocketLoadEventData + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeSimplePlaylistObjectCopyWith<$Res>? get collection { + if (_value.collection == null) { + return null; + } + + return $SpotubeSimplePlaylistObjectCopyWith<$Res>(_value.collection!, + (value) { + return _then(_value.copyWith(collection: value)); + }); + } } /// @nodoc @@ -209,8 +211,7 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> class _$WebSocketLoadEventDataPlaylistImpl extends WebSocketLoadEventDataPlaylist { _$WebSocketLoadEventDataPlaylistImpl( - {@JsonKey(name: 'tracks', toJson: _tracksJson) - required final List tracks, + {required final List tracks, this.collection, this.initialIndex, final String? $type}) @@ -222,17 +223,16 @@ class _$WebSocketLoadEventDataPlaylistImpl Map json) => _$$WebSocketLoadEventDataPlaylistImplFromJson(json); - final List _tracks; + final List _tracks; @override - @JsonKey(name: 'tracks', toJson: _tracksJson) - List get tracks { + List get tracks { if (_tracks is EqualUnmodifiableListView) return _tracks; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tracks); } @override - final PlaylistSimple? collection; + final SpotubeSimplePlaylistObject? collection; @override final int? initialIndex; @@ -274,15 +274,11 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult when({ - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex) playlist, - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex) album, }) { return playlist(tracks, collection, initialIndex); @@ -291,15 +287,11 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, }) { return playlist?.call(tracks, collection, initialIndex); @@ -308,15 +300,11 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, required TResult orElse(), }) { @@ -367,9 +355,8 @@ class _$WebSocketLoadEventDataPlaylistImpl abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData { factory WebSocketLoadEventDataPlaylist( - {@JsonKey(name: 'tracks', toJson: _tracksJson) - required final List tracks, - final PlaylistSimple? collection, + {required final List tracks, + final SpotubeSimplePlaylistObject? collection, final int? initialIndex}) = _$WebSocketLoadEventDataPlaylistImpl; WebSocketLoadEventDataPlaylist._() : super._(); @@ -377,10 +364,9 @@ abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData { _$WebSocketLoadEventDataPlaylistImpl.fromJson; @override - @JsonKey(name: 'tracks', toJson: _tracksJson) - List get tracks; + List get tracks; @override - PlaylistSimple? get collection; + SpotubeSimplePlaylistObject? get collection; @override int? get initialIndex; @@ -403,9 +389,11 @@ abstract class _$$WebSocketLoadEventDataAlbumImplCopyWith<$Res> @override @useResult $Res call( - {@JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, + {List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex}); + + $SpotubeSimpleAlbumObjectCopyWith<$Res>? get collection; } /// @nodoc @@ -431,25 +419,38 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res> tracks: null == tracks ? _value._tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, collection: freezed == collection ? _value.collection : collection // ignore: cast_nullable_to_non_nullable - as AlbumSimple?, + as SpotubeSimpleAlbumObject?, initialIndex: freezed == initialIndex ? _value.initialIndex : initialIndex // ignore: cast_nullable_to_non_nullable as int?, )); } + + /// Create a copy of WebSocketLoadEventData + /// with the given fields replaced by the non-null parameter values. + @override + @pragma('vm:prefer-inline') + $SpotubeSimpleAlbumObjectCopyWith<$Res>? get collection { + if (_value.collection == null) { + return null; + } + + return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.collection!, (value) { + return _then(_value.copyWith(collection: value)); + }); + } } /// @nodoc @JsonSerializable() class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { _$WebSocketLoadEventDataAlbumImpl( - {@JsonKey(name: 'tracks', toJson: _tracksJson) - required final List tracks, + {required final List tracks, this.collection, this.initialIndex, final String? $type}) @@ -461,17 +462,16 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { Map json) => _$$WebSocketLoadEventDataAlbumImplFromJson(json); - final List _tracks; + final List _tracks; @override - @JsonKey(name: 'tracks', toJson: _tracksJson) - List get tracks { + List get tracks { if (_tracks is EqualUnmodifiableListView) return _tracks; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tracks); } @override - final AlbumSimple? collection; + final SpotubeSimpleAlbumObject? collection; @override final int? initialIndex; @@ -512,15 +512,11 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult when({ - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex) playlist, - required TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex) + required TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex) album, }) { return album(tracks, collection, initialIndex); @@ -529,15 +525,11 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult? Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult? Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, }) { return album?.call(tracks, collection, initialIndex); @@ -546,15 +538,11 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - PlaylistSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex)? playlist, - TResult Function( - @JsonKey(name: 'tracks', toJson: _tracksJson) List tracks, - AlbumSimple? collection, - int? initialIndex)? + TResult Function(List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex)? album, required TResult orElse(), }) { @@ -605,9 +593,8 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData { factory WebSocketLoadEventDataAlbum( - {@JsonKey(name: 'tracks', toJson: _tracksJson) - required final List tracks, - final AlbumSimple? collection, + {required final List tracks, + final SpotubeSimpleAlbumObject? collection, final int? initialIndex}) = _$WebSocketLoadEventDataAlbumImpl; WebSocketLoadEventDataAlbum._() : super._(); @@ -615,10 +602,9 @@ abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData { _$WebSocketLoadEventDataAlbumImpl.fromJson; @override - @JsonKey(name: 'tracks', toJson: _tracksJson) - List get tracks; + List get tracks; @override - AlbumSimple? get collection; + SpotubeSimpleAlbumObject? get collection; @override int? get initialIndex; diff --git a/lib/models/connect/connect.g.dart b/lib/models/connect/connect.g.dart index 10f46c65..465dc42e 100644 --- a/lib/models/connect/connect.g.dart +++ b/lib/models/connect/connect.g.dart @@ -10,11 +10,12 @@ _$WebSocketLoadEventDataPlaylistImpl _$$WebSocketLoadEventDataPlaylistImplFromJson(Map json) => _$WebSocketLoadEventDataPlaylistImpl( tracks: (json['tracks'] as List) - .map((e) => Track.fromJson(Map.from(e as Map))) + .map((e) => SpotubeFullTrackObject.fromJson( + Map.from(e as Map))) .toList(), collection: json['collection'] == null ? null - : PlaylistSimple.fromJson( + : SpotubeSimplePlaylistObject.fromJson( Map.from(json['collection'] as Map)), initialIndex: (json['initialIndex'] as num?)?.toInt(), $type: json['runtimeType'] as String?, @@ -23,7 +24,7 @@ _$WebSocketLoadEventDataPlaylistImpl Map _$$WebSocketLoadEventDataPlaylistImplToJson( _$WebSocketLoadEventDataPlaylistImpl instance) => { - 'tracks': _tracksJson(instance.tracks), + 'tracks': instance.tracks.map((e) => e.toJson()).toList(), 'collection': instance.collection?.toJson(), 'initialIndex': instance.initialIndex, 'runtimeType': instance.$type, @@ -33,11 +34,12 @@ _$WebSocketLoadEventDataAlbumImpl _$$WebSocketLoadEventDataAlbumImplFromJson( Map json) => _$WebSocketLoadEventDataAlbumImpl( tracks: (json['tracks'] as List) - .map((e) => Track.fromJson(Map.from(e as Map))) + .map((e) => SpotubeFullTrackObject.fromJson( + Map.from(e as Map))) .toList(), collection: json['collection'] == null ? null - : AlbumSimple.fromJson( + : SpotubeSimpleAlbumObject.fromJson( Map.from(json['collection'] as Map)), initialIndex: (json['initialIndex'] as num?)?.toInt(), $type: json['runtimeType'] as String?, @@ -46,7 +48,7 @@ _$WebSocketLoadEventDataAlbumImpl _$$WebSocketLoadEventDataAlbumImplFromJson( Map _$$WebSocketLoadEventDataAlbumImplToJson( _$WebSocketLoadEventDataAlbumImpl instance) => { - 'tracks': _tracksJson(instance.tracks), + 'tracks': instance.tracks.map((e) => e.toJson()).toList(), 'collection': instance.collection?.toJson(), 'initialIndex': instance.initialIndex, 'runtimeType': instance.$type, diff --git a/lib/models/connect/load.dart b/lib/models/connect/load.dart index bf0e164d..36d3c3ba 100644 --- a/lib/models/connect/load.dart +++ b/lib/models/connect/load.dart @@ -1,22 +1,18 @@ part of 'connect.dart'; -List> _tracksJson(List tracks) { - return tracks.map((e) => e.toJson()).toList(); -} - @freezed class WebSocketLoadEventData with _$WebSocketLoadEventData { const WebSocketLoadEventData._(); factory WebSocketLoadEventData.playlist({ - @JsonKey(name: 'tracks', toJson: _tracksJson) required List tracks, - PlaylistSimple? collection, + required List tracks, + SpotubeSimplePlaylistObject? collection, int? initialIndex, }) = WebSocketLoadEventDataPlaylist; factory WebSocketLoadEventData.album({ - @JsonKey(name: 'tracks', toJson: _tracksJson) required List tracks, - AlbumSimple? collection, + required List tracks, + SpotubeSimpleAlbumObject? collection, int? initialIndex, }) = WebSocketLoadEventDataAlbum; diff --git a/lib/models/connect/ws_event.dart b/lib/models/connect/ws_event.dart index d1047646..7867f686 100644 --- a/lib/models/connect/ws_event.dart +++ b/lib/models/connect/ws_event.dart @@ -338,13 +338,16 @@ class WebSocketRemoveTrackEvent extends WebSocketEvent { WebSocketRemoveTrackEvent(String data) : super(WsEvent.removeTrack, data); } -class WebSocketAddTrackEvent extends WebSocketEvent { - WebSocketAddTrackEvent(Track data) : super(WsEvent.addTrack, data); +class WebSocketAddTrackEvent extends WebSocketEvent { + WebSocketAddTrackEvent(SpotubeFullTrackObject data) + : super(WsEvent.addTrack, data); WebSocketAddTrackEvent.fromJson(Map json) : super( WsEvent.addTrack, - Track.fromJson(json["data"] as Map), + SpotubeFullTrackObject.fromJson( + json["data"] as Map, + ), ); } diff --git a/lib/models/current_playlist.dart b/lib/models/current_playlist.dart deleted file mode 100644 index 7e55e393..00000000 --- a/lib/models/current_playlist.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; - -class CurrentPlaylist { - List? _tempTrack; - List tracks; - String id; - String name; - String thumbnail; - bool isLocal; - - CurrentPlaylist({ - required this.tracks, - required this.id, - required this.name, - required this.thumbnail, - this.isLocal = false, - }); - - static CurrentPlaylist fromJson(Map map, Ref ref) { - return CurrentPlaylist( - id: map["id"], - tracks: List.castFrom(map["tracks"] - .map( - (track) => map["isLocal"] == true - ? SourcedTrack.fromJson(track, ref: ref) - : Track.fromJson(track), - ) - .toList()), - name: map["name"], - thumbnail: map["thumbnail"], - isLocal: map["isLocal"], - ); - } - - List get trackIds => tracks.map((e) => e.id!).toList(); - - bool shuffle(Track? topTrack) { - // won't shuffle if already shuffled - if (_tempTrack == null) { - _tempTrack = [...tracks]; - tracks = List.from(tracks)..shuffle(); - if (topTrack != null) { - tracks.remove(topTrack); - tracks.insert(0, topTrack); - } - return true; - } - return false; - } - - bool unshuffle() { - // without _tempTracks unshuffling can't be done - if (_tempTrack != null) { - tracks = [..._tempTrack!]; - _tempTrack = null; - return true; - } - return false; - } - - Map toJson() { - return { - "id": id, - "name": name, - "tracks": tracks - .map((track) => - track is SourcedTrack ? track.toJson() : track.toJson()) - .toList(), - "thumbnail": thumbnail, - "isLocal": isLocal, - }; - } -} diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 20aa649a..8186fd92 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -13,6 +13,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart' show ThemeMode, Colors; import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/database/database.steps.dart'; import 'package:spotube/models/lyrics.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:spotube/services/sourced_track/enums.dart'; @@ -43,6 +44,7 @@ part 'typeconverters/locale.dart'; part 'typeconverters/string_list.dart'; part 'typeconverters/encrypted_text.dart'; part 'typeconverters/map.dart'; +part 'typeconverters/map_list.dart'; part 'typeconverters/subtitle.dart'; @DriftDatabase( @@ -54,8 +56,6 @@ part 'typeconverters/subtitle.dart'; SkipSegmentTable, SourceMatchTable, AudioPlayerStateTable, - PlaylistTable, - PlaylistMediaTable, HistoryTable, LyricsTable, MetadataPluginsTable, diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 02f75640..35905ce4 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -2926,9 +2926,22 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable type: DriftSqlType.string, requiredDuringInsert: true) .withConverter>( $AudioPlayerStateTableTable.$convertercollections); + static const VerificationMeta _tracksMeta = const VerificationMeta('tracks'); + @override + late final GeneratedColumnWithTypeConverter, String> + tracks = GeneratedColumn('tracks', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true) + .withConverter>( + $AudioPlayerStateTableTable.$convertertracks); + static const VerificationMeta _currentIndexMeta = + const VerificationMeta('currentIndex'); + @override + late final GeneratedColumn currentIndex = GeneratedColumn( + 'current_index', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); @override List get $columns => - [id, playing, loopMode, shuffled, collections]; + [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -2957,6 +2970,15 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable context.missing(_shuffledMeta); } context.handle(_collectionsMeta, const VerificationResult.success()); + context.handle(_tracksMeta, const VerificationResult.success()); + if (data.containsKey('current_index')) { + context.handle( + _currentIndexMeta, + currentIndex.isAcceptableOrUnknown( + data['current_index']!, _currentIndexMeta)); + } else if (isInserting) { + context.missing(_currentIndexMeta); + } return context; } @@ -2979,6 +3001,11 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable collections: $AudioPlayerStateTableTable.$convertercollections.fromSql( attachedDatabase.typeMapping.read( DriftSqlType.string, data['${effectivePrefix}collections'])!), + tracks: $AudioPlayerStateTableTable.$convertertracks.fromSql( + attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tracks'])!), + currentIndex: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_index'])!, ); } @@ -2991,6 +3018,8 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable const EnumNameConverter(PlaylistMode.values); static TypeConverter, String> $convertercollections = const StringListConverter(); + static TypeConverter, String> $convertertracks = + const SpotubeTrackObjectListConverter(); } class AudioPlayerStateTableData extends DataClass @@ -3000,12 +3029,16 @@ class AudioPlayerStateTableData extends DataClass final PlaylistMode loopMode; final bool shuffled; final List collections; + final List tracks; + final int currentIndex; const AudioPlayerStateTableData( {required this.id, required this.playing, required this.loopMode, required this.shuffled, - required this.collections}); + required this.collections, + required this.tracks, + required this.currentIndex}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -3020,6 +3053,11 @@ class AudioPlayerStateTableData extends DataClass map['collections'] = Variable( $AudioPlayerStateTableTable.$convertercollections.toSql(collections)); } + { + map['tracks'] = Variable( + $AudioPlayerStateTableTable.$convertertracks.toSql(tracks)); + } + map['current_index'] = Variable(currentIndex); return map; } @@ -3030,6 +3068,8 @@ class AudioPlayerStateTableData extends DataClass loopMode: Value(loopMode), shuffled: Value(shuffled), collections: Value(collections), + tracks: Value(tracks), + currentIndex: Value(currentIndex), ); } @@ -3043,6 +3083,8 @@ class AudioPlayerStateTableData extends DataClass .fromJson(serializer.fromJson(json['loopMode'])), shuffled: serializer.fromJson(json['shuffled']), collections: serializer.fromJson>(json['collections']), + tracks: serializer.fromJson>(json['tracks']), + currentIndex: serializer.fromJson(json['currentIndex']), ); } @override @@ -3055,6 +3097,8 @@ class AudioPlayerStateTableData extends DataClass $AudioPlayerStateTableTable.$converterloopMode.toJson(loopMode)), 'shuffled': serializer.toJson(shuffled), 'collections': serializer.toJson>(collections), + 'tracks': serializer.toJson>(tracks), + 'currentIndex': serializer.toJson(currentIndex), }; } @@ -3063,13 +3107,17 @@ class AudioPlayerStateTableData extends DataClass bool? playing, PlaylistMode? loopMode, bool? shuffled, - List? collections}) => + List? collections, + List? tracks, + int? currentIndex}) => AudioPlayerStateTableData( id: id ?? this.id, playing: playing ?? this.playing, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, ); AudioPlayerStateTableData copyWithCompanion( AudioPlayerStateTableCompanion data) { @@ -3080,6 +3128,10 @@ class AudioPlayerStateTableData extends DataClass shuffled: data.shuffled.present ? data.shuffled.value : this.shuffled, collections: data.collections.present ? data.collections.value : this.collections, + tracks: data.tracks.present ? data.tracks.value : this.tracks, + currentIndex: data.currentIndex.present + ? data.currentIndex.value + : this.currentIndex, ); } @@ -3090,13 +3142,16 @@ class AudioPlayerStateTableData extends DataClass ..write('playing: $playing, ') ..write('loopMode: $loopMode, ') ..write('shuffled: $shuffled, ') - ..write('collections: $collections') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, playing, loopMode, shuffled, collections); + int get hashCode => Object.hash( + id, playing, loopMode, shuffled, collections, tracks, currentIndex); @override bool operator ==(Object other) => identical(this, other) || @@ -3105,7 +3160,9 @@ class AudioPlayerStateTableData extends DataClass other.playing == this.playing && other.loopMode == this.loopMode && other.shuffled == this.shuffled && - other.collections == this.collections); + other.collections == this.collections && + other.tracks == this.tracks && + other.currentIndex == this.currentIndex); } class AudioPlayerStateTableCompanion @@ -3115,12 +3172,16 @@ class AudioPlayerStateTableCompanion final Value loopMode; final Value shuffled; final Value> collections; + final Value> tracks; + final Value currentIndex; const AudioPlayerStateTableCompanion({ this.id = const Value.absent(), this.playing = const Value.absent(), this.loopMode = const Value.absent(), this.shuffled = const Value.absent(), this.collections = const Value.absent(), + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), }); AudioPlayerStateTableCompanion.insert({ this.id = const Value.absent(), @@ -3128,16 +3189,22 @@ class AudioPlayerStateTableCompanion required PlaylistMode loopMode, required bool shuffled, required List collections, + required List tracks, + required int currentIndex, }) : playing = Value(playing), loopMode = Value(loopMode), shuffled = Value(shuffled), - collections = Value(collections); + collections = Value(collections), + tracks = Value(tracks), + currentIndex = Value(currentIndex); static Insertable custom({ Expression? id, Expression? playing, Expression? loopMode, Expression? shuffled, Expression? collections, + Expression? tracks, + Expression? currentIndex, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -3145,6 +3212,8 @@ class AudioPlayerStateTableCompanion if (loopMode != null) 'loop_mode': loopMode, if (shuffled != null) 'shuffled': shuffled, if (collections != null) 'collections': collections, + if (tracks != null) 'tracks': tracks, + if (currentIndex != null) 'current_index': currentIndex, }); } @@ -3153,13 +3222,17 @@ class AudioPlayerStateTableCompanion Value? playing, Value? loopMode, Value? shuffled, - Value>? collections}) { + Value>? collections, + Value>? tracks, + Value? currentIndex}) { return AudioPlayerStateTableCompanion( id: id ?? this.id, playing: playing ?? this.playing, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, ); } @@ -3184,6 +3257,13 @@ class AudioPlayerStateTableCompanion .$convertercollections .toSql(collections.value)); } + if (tracks.present) { + map['tracks'] = Variable( + $AudioPlayerStateTableTable.$convertertracks.toSql(tracks.value)); + } + if (currentIndex.present) { + map['current_index'] = Variable(currentIndex.value); + } return map; } @@ -3194,557 +3274,9 @@ class AudioPlayerStateTableCompanion ..write('playing: $playing, ') ..write('loopMode: $loopMode, ') ..write('shuffled: $shuffled, ') - ..write('collections: $collections') - ..write(')')) - .toString(); - } -} - -class $PlaylistTableTable extends PlaylistTable - with TableInfo<$PlaylistTableTable, PlaylistTableData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $PlaylistTableTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _audioPlayerStateIdMeta = - const VerificationMeta('audioPlayerStateId'); - @override - late final GeneratedColumn audioPlayerStateId = GeneratedColumn( - 'audio_player_state_id', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES audio_player_state_table (id)')); - static const VerificationMeta _indexMeta = const VerificationMeta('index'); - @override - late final GeneratedColumn index = GeneratedColumn( - 'index', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); - @override - List get $columns => [id, audioPlayerStateId, index]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'playlist_table'; - @override - VerificationContext validateIntegrity(Insertable instance, - {bool isInserting = false}) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } - if (data.containsKey('audio_player_state_id')) { - context.handle( - _audioPlayerStateIdMeta, - audioPlayerStateId.isAcceptableOrUnknown( - data['audio_player_state_id']!, _audioPlayerStateIdMeta)); - } else if (isInserting) { - context.missing(_audioPlayerStateIdMeta); - } - if (data.containsKey('index')) { - context.handle( - _indexMeta, index.isAcceptableOrUnknown(data['index']!, _indexMeta)); - } else if (isInserting) { - context.missing(_indexMeta); - } - return context; - } - - @override - Set get $primaryKey => {id}; - @override - PlaylistTableData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlaylistTableData( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - audioPlayerStateId: attachedDatabase.typeMapping.read( - DriftSqlType.int, data['${effectivePrefix}audio_player_state_id'])!, - index: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}index'])!, - ); - } - - @override - $PlaylistTableTable createAlias(String alias) { - return $PlaylistTableTable(attachedDatabase, alias); - } -} - -class PlaylistTableData extends DataClass - implements Insertable { - final int id; - final int audioPlayerStateId; - final int index; - const PlaylistTableData( - {required this.id, - required this.audioPlayerStateId, - required this.index}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['audio_player_state_id'] = Variable(audioPlayerStateId); - map['index'] = Variable(index); - return map; - } - - PlaylistTableCompanion toCompanion(bool nullToAbsent) { - return PlaylistTableCompanion( - id: Value(id), - audioPlayerStateId: Value(audioPlayerStateId), - index: Value(index), - ); - } - - factory PlaylistTableData.fromJson(Map json, - {ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return PlaylistTableData( - id: serializer.fromJson(json['id']), - audioPlayerStateId: serializer.fromJson(json['audioPlayerStateId']), - index: serializer.fromJson(json['index']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'audioPlayerStateId': serializer.toJson(audioPlayerStateId), - 'index': serializer.toJson(index), - }; - } - - PlaylistTableData copyWith({int? id, int? audioPlayerStateId, int? index}) => - PlaylistTableData( - id: id ?? this.id, - audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, - index: index ?? this.index, - ); - PlaylistTableData copyWithCompanion(PlaylistTableCompanion data) { - return PlaylistTableData( - id: data.id.present ? data.id.value : this.id, - audioPlayerStateId: data.audioPlayerStateId.present - ? data.audioPlayerStateId.value - : this.audioPlayerStateId, - index: data.index.present ? data.index.value : this.index, - ); - } - - @override - String toString() { - return (StringBuffer('PlaylistTableData(') - ..write('id: $id, ') - ..write('audioPlayerStateId: $audioPlayerStateId, ') - ..write('index: $index') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, audioPlayerStateId, index); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is PlaylistTableData && - other.id == this.id && - other.audioPlayerStateId == this.audioPlayerStateId && - other.index == this.index); -} - -class PlaylistTableCompanion extends UpdateCompanion { - final Value id; - final Value audioPlayerStateId; - final Value index; - const PlaylistTableCompanion({ - this.id = const Value.absent(), - this.audioPlayerStateId = const Value.absent(), - this.index = const Value.absent(), - }); - PlaylistTableCompanion.insert({ - this.id = const Value.absent(), - required int audioPlayerStateId, - required int index, - }) : audioPlayerStateId = Value(audioPlayerStateId), - index = Value(index); - static Insertable custom({ - Expression? id, - Expression? audioPlayerStateId, - Expression? index, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (audioPlayerStateId != null) - 'audio_player_state_id': audioPlayerStateId, - if (index != null) 'index': index, - }); - } - - PlaylistTableCompanion copyWith( - {Value? id, Value? audioPlayerStateId, Value? index}) { - return PlaylistTableCompanion( - id: id ?? this.id, - audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, - index: index ?? this.index, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (audioPlayerStateId.present) { - map['audio_player_state_id'] = Variable(audioPlayerStateId.value); - } - if (index.present) { - map['index'] = Variable(index.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('PlaylistTableCompanion(') - ..write('id: $id, ') - ..write('audioPlayerStateId: $audioPlayerStateId, ') - ..write('index: $index') - ..write(')')) - .toString(); - } -} - -class $PlaylistMediaTableTable extends PlaylistMediaTable - with TableInfo<$PlaylistMediaTableTable, PlaylistMediaTableData> { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - $PlaylistMediaTableTable(this.attachedDatabase, [this._alias]); - static const VerificationMeta _idMeta = const VerificationMeta('id'); - @override - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - static const VerificationMeta _playlistIdMeta = - const VerificationMeta('playlistId'); - @override - late final GeneratedColumn playlistId = GeneratedColumn( - 'playlist_id', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: true, - defaultConstraints: - GeneratedColumn.constraintIsAlways('REFERENCES playlist_table (id)')); - static const VerificationMeta _uriMeta = const VerificationMeta('uri'); - @override - late final GeneratedColumn uri = GeneratedColumn( - 'uri', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - static const VerificationMeta _extrasMeta = const VerificationMeta('extras'); - @override - late final GeneratedColumnWithTypeConverter?, String> - extras = GeneratedColumn('extras', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PlaylistMediaTableTable.$converterextrasn); - static const VerificationMeta _httpHeadersMeta = - const VerificationMeta('httpHeaders'); - @override - late final GeneratedColumnWithTypeConverter?, String> - httpHeaders = GeneratedColumn('http_headers', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false) - .withConverter?>( - $PlaylistMediaTableTable.$converterhttpHeadersn); - @override - List get $columns => - [id, playlistId, uri, extras, httpHeaders]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'playlist_media_table'; - @override - VerificationContext validateIntegrity( - Insertable instance, - {bool isInserting = false}) { - final context = VerificationContext(); - final data = instance.toColumns(true); - if (data.containsKey('id')) { - context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); - } - if (data.containsKey('playlist_id')) { - context.handle( - _playlistIdMeta, - playlistId.isAcceptableOrUnknown( - data['playlist_id']!, _playlistIdMeta)); - } else if (isInserting) { - context.missing(_playlistIdMeta); - } - if (data.containsKey('uri')) { - context.handle( - _uriMeta, uri.isAcceptableOrUnknown(data['uri']!, _uriMeta)); - } else if (isInserting) { - context.missing(_uriMeta); - } - context.handle(_extrasMeta, const VerificationResult.success()); - context.handle(_httpHeadersMeta, const VerificationResult.success()); - return context; - } - - @override - Set get $primaryKey => {id}; - @override - PlaylistMediaTableData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlaylistMediaTableData( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - playlistId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}playlist_id'])!, - uri: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}uri'])!, - extras: $PlaylistMediaTableTable.$converterextrasn.fromSql( - attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extras'])), - httpHeaders: $PlaylistMediaTableTable.$converterhttpHeadersn.fromSql( - attachedDatabase.typeMapping.read( - DriftSqlType.string, data['${effectivePrefix}http_headers'])), - ); - } - - @override - $PlaylistMediaTableTable createAlias(String alias) { - return $PlaylistMediaTableTable(attachedDatabase, alias); - } - - static TypeConverter, String> $converterextras = - const MapTypeConverter(); - static TypeConverter?, String?> $converterextrasn = - NullAwareTypeConverter.wrap($converterextras); - static TypeConverter, String> $converterhttpHeaders = - const MapTypeConverter(); - static TypeConverter?, String?> $converterhttpHeadersn = - NullAwareTypeConverter.wrap($converterhttpHeaders); -} - -class PlaylistMediaTableData extends DataClass - implements Insertable { - final int id; - final int playlistId; - final String uri; - final Map? extras; - final Map? httpHeaders; - const PlaylistMediaTableData( - {required this.id, - required this.playlistId, - required this.uri, - this.extras, - this.httpHeaders}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['playlist_id'] = Variable(playlistId); - map['uri'] = Variable(uri); - if (!nullToAbsent || extras != null) { - map['extras'] = Variable( - $PlaylistMediaTableTable.$converterextrasn.toSql(extras)); - } - if (!nullToAbsent || httpHeaders != null) { - map['http_headers'] = Variable( - $PlaylistMediaTableTable.$converterhttpHeadersn.toSql(httpHeaders)); - } - return map; - } - - PlaylistMediaTableCompanion toCompanion(bool nullToAbsent) { - return PlaylistMediaTableCompanion( - id: Value(id), - playlistId: Value(playlistId), - uri: Value(uri), - extras: - extras == null && nullToAbsent ? const Value.absent() : Value(extras), - httpHeaders: httpHeaders == null && nullToAbsent - ? const Value.absent() - : Value(httpHeaders), - ); - } - - factory PlaylistMediaTableData.fromJson(Map json, - {ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return PlaylistMediaTableData( - id: serializer.fromJson(json['id']), - playlistId: serializer.fromJson(json['playlistId']), - uri: serializer.fromJson(json['uri']), - extras: serializer.fromJson?>(json['extras']), - httpHeaders: - serializer.fromJson?>(json['httpHeaders']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'playlistId': serializer.toJson(playlistId), - 'uri': serializer.toJson(uri), - 'extras': serializer.toJson?>(extras), - 'httpHeaders': serializer.toJson?>(httpHeaders), - }; - } - - PlaylistMediaTableData copyWith( - {int? id, - int? playlistId, - String? uri, - Value?> extras = const Value.absent(), - Value?> httpHeaders = const Value.absent()}) => - PlaylistMediaTableData( - id: id ?? this.id, - playlistId: playlistId ?? this.playlistId, - uri: uri ?? this.uri, - extras: extras.present ? extras.value : this.extras, - httpHeaders: httpHeaders.present ? httpHeaders.value : this.httpHeaders, - ); - PlaylistMediaTableData copyWithCompanion(PlaylistMediaTableCompanion data) { - return PlaylistMediaTableData( - id: data.id.present ? data.id.value : this.id, - playlistId: - data.playlistId.present ? data.playlistId.value : this.playlistId, - uri: data.uri.present ? data.uri.value : this.uri, - extras: data.extras.present ? data.extras.value : this.extras, - httpHeaders: - data.httpHeaders.present ? data.httpHeaders.value : this.httpHeaders, - ); - } - - @override - String toString() { - return (StringBuffer('PlaylistMediaTableData(') - ..write('id: $id, ') - ..write('playlistId: $playlistId, ') - ..write('uri: $uri, ') - ..write('extras: $extras, ') - ..write('httpHeaders: $httpHeaders') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, playlistId, uri, extras, httpHeaders); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is PlaylistMediaTableData && - other.id == this.id && - other.playlistId == this.playlistId && - other.uri == this.uri && - other.extras == this.extras && - other.httpHeaders == this.httpHeaders); -} - -class PlaylistMediaTableCompanion - extends UpdateCompanion { - final Value id; - final Value playlistId; - final Value uri; - final Value?> extras; - final Value?> httpHeaders; - const PlaylistMediaTableCompanion({ - this.id = const Value.absent(), - this.playlistId = const Value.absent(), - this.uri = const Value.absent(), - this.extras = const Value.absent(), - this.httpHeaders = const Value.absent(), - }); - PlaylistMediaTableCompanion.insert({ - this.id = const Value.absent(), - required int playlistId, - required String uri, - this.extras = const Value.absent(), - this.httpHeaders = const Value.absent(), - }) : playlistId = Value(playlistId), - uri = Value(uri); - static Insertable custom({ - Expression? id, - Expression? playlistId, - Expression? uri, - Expression? extras, - Expression? httpHeaders, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (playlistId != null) 'playlist_id': playlistId, - if (uri != null) 'uri': uri, - if (extras != null) 'extras': extras, - if (httpHeaders != null) 'http_headers': httpHeaders, - }); - } - - PlaylistMediaTableCompanion copyWith( - {Value? id, - Value? playlistId, - Value? uri, - Value?>? extras, - Value?>? httpHeaders}) { - return PlaylistMediaTableCompanion( - id: id ?? this.id, - playlistId: playlistId ?? this.playlistId, - uri: uri ?? this.uri, - extras: extras ?? this.extras, - httpHeaders: httpHeaders ?? this.httpHeaders, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (playlistId.present) { - map['playlist_id'] = Variable(playlistId.value); - } - if (uri.present) { - map['uri'] = Variable(uri.value); - } - if (extras.present) { - map['extras'] = Variable( - $PlaylistMediaTableTable.$converterextrasn.toSql(extras.value)); - } - if (httpHeaders.present) { - map['http_headers'] = Variable($PlaylistMediaTableTable - .$converterhttpHeadersn - .toSql(httpHeaders.value)); - } - return map; - } - - @override - String toString() { - return (StringBuffer('PlaylistMediaTableCompanion(') - ..write('id: $id, ') - ..write('playlistId: $playlistId, ') - ..write('uri: $uri, ') - ..write('extras: $extras, ') - ..write('httpHeaders: $httpHeaders') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') ..write(')')) .toString(); } @@ -4769,9 +4301,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { $SourceMatchTableTable(this); late final $AudioPlayerStateTableTable audioPlayerStateTable = $AudioPlayerStateTableTable(this); - late final $PlaylistTableTable playlistTable = $PlaylistTableTable(this); - late final $PlaylistMediaTableTable playlistMediaTable = - $PlaylistMediaTableTable(this); late final $HistoryTableTable historyTable = $HistoryTableTable(this); late final $LyricsTableTable lyricsTable = $LyricsTableTable(this); late final $MetadataPluginsTableTable metadataPluginsTable = @@ -4792,8 +4321,6 @@ abstract class _$AppDatabase extends GeneratedDatabase { skipSegmentTable, sourceMatchTable, audioPlayerStateTable, - playlistTable, - playlistMediaTable, historyTable, lyricsTable, metadataPluginsTable, @@ -6224,6 +5751,8 @@ typedef $$AudioPlayerStateTableTableCreateCompanionBuilder required PlaylistMode loopMode, required bool shuffled, required List collections, + required List tracks, + required int currentIndex, }); typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder = AudioPlayerStateTableCompanion Function({ @@ -6232,29 +5761,10 @@ typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder Value loopMode, Value shuffled, Value> collections, + Value> tracks, + Value currentIndex, }); -final class $$AudioPlayerStateTableTableReferences extends BaseReferences< - _$AppDatabase, $AudioPlayerStateTableTable, AudioPlayerStateTableData> { - $$AudioPlayerStateTableTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static MultiTypedResultKey<$PlaylistTableTable, List> - _playlistTableRefsTable(_$AppDatabase db) => - MultiTypedResultKey.fromTable(db.playlistTable, - aliasName: $_aliasNameGenerator(db.audioPlayerStateTable.id, - db.playlistTable.audioPlayerStateId)); - - $$PlaylistTableTableProcessedTableManager get playlistTableRefs { - final manager = $$PlaylistTableTableTableManager($_db, $_db.playlistTable) - .filter((f) => f.audioPlayerStateId.id($_item.id)); - - final cache = $_typedResult.readTableOrNull(_playlistTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); - } -} - class $$AudioPlayerStateTableTableFilterComposer extends Composer<_$AppDatabase, $AudioPlayerStateTableTable> { $$AudioPlayerStateTableTableFilterComposer({ @@ -6283,26 +5793,14 @@ class $$AudioPlayerStateTableTableFilterComposer column: $table.collections, builder: (column) => ColumnWithTypeConverterFilters(column)); - Expression playlistTableRefs( - Expression Function($$PlaylistTableTableFilterComposer f) f) { - final $$PlaylistTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playlistTable, - getReferencedColumn: (t) => t.audioPlayerStateId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistTableTableFilterComposer( - $db: $db, - $table: $db.playlistTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return f(composer); - } + ColumnWithTypeConverterFilters, + List, String> + get tracks => $composableBuilder( + column: $table.tracks, + builder: (column) => ColumnWithTypeConverterFilters(column)); + + ColumnFilters get currentIndex => $composableBuilder( + column: $table.currentIndex, builder: (column) => ColumnFilters(column)); } class $$AudioPlayerStateTableTableOrderingComposer @@ -6328,6 +5826,13 @@ class $$AudioPlayerStateTableTableOrderingComposer ColumnOrderings get collections => $composableBuilder( column: $table.collections, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get tracks => $composableBuilder( + column: $table.tracks, builder: (column) => ColumnOrderings(column)); + + ColumnOrderings get currentIndex => $composableBuilder( + column: $table.currentIndex, + builder: (column) => ColumnOrderings(column)); } class $$AudioPlayerStateTableTableAnnotationComposer @@ -6355,26 +5860,12 @@ class $$AudioPlayerStateTableTableAnnotationComposer $composableBuilder( column: $table.collections, builder: (column) => column); - Expression playlistTableRefs( - Expression Function($$PlaylistTableTableAnnotationComposer a) f) { - final $$PlaylistTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playlistTable, - getReferencedColumn: (t) => t.audioPlayerStateId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistTableTableAnnotationComposer( - $db: $db, - $table: $db.playlistTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return f(composer); - } + GeneratedColumnWithTypeConverter, String> + get tracks => $composableBuilder( + column: $table.tracks, builder: (column) => column); + + GeneratedColumn get currentIndex => $composableBuilder( + column: $table.currentIndex, builder: (column) => column); } class $$AudioPlayerStateTableTableTableManager extends RootTableManager< @@ -6386,9 +5877,13 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< $$AudioPlayerStateTableTableAnnotationComposer, $$AudioPlayerStateTableTableCreateCompanionBuilder, $$AudioPlayerStateTableTableUpdateCompanionBuilder, - (AudioPlayerStateTableData, $$AudioPlayerStateTableTableReferences), + ( + AudioPlayerStateTableData, + BaseReferences<_$AppDatabase, $AudioPlayerStateTableTable, + AudioPlayerStateTableData> + ), AudioPlayerStateTableData, - PrefetchHooks Function({bool playlistTableRefs})> { + PrefetchHooks Function()> { $$AudioPlayerStateTableTableTableManager( _$AppDatabase db, $AudioPlayerStateTableTable table) : super(TableManagerState( @@ -6409,6 +5904,8 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< Value loopMode = const Value.absent(), Value shuffled = const Value.absent(), Value> collections = const Value.absent(), + Value> tracks = const Value.absent(), + Value currentIndex = const Value.absent(), }) => AudioPlayerStateTableCompanion( id: id, @@ -6416,6 +5913,8 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< loopMode: loopMode, shuffled: shuffled, collections: collections, + tracks: tracks, + currentIndex: currentIndex, ), createCompanionCallback: ({ Value id = const Value.absent(), @@ -6423,6 +5922,8 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< required PlaylistMode loopMode, required bool shuffled, required List collections, + required List tracks, + required int currentIndex, }) => AudioPlayerStateTableCompanion.insert( id: id, @@ -6430,39 +5931,13 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< loopMode: loopMode, shuffled: shuffled, collections: collections, + tracks: tracks, + currentIndex: currentIndex, ), withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$AudioPlayerStateTableTableReferences(db, table, e) - )) + .map((e) => (e.readTable(table), BaseReferences(db, table, e))) .toList(), - prefetchHooksCallback: ({playlistTableRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (playlistTableRefs) db.playlistTable - ], - addJoins: null, - getPrefetchedDataCallback: (items) async { - return [ - if (playlistTableRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$AudioPlayerStateTableTableReferences - ._playlistTableRefsTable(db), - managerFromTypedResult: (p0) => - $$AudioPlayerStateTableTableReferences( - db, table, p0) - .playlistTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.audioPlayerStateId == item.id), - typedResults: items) - ]; - }, - ); - }, + prefetchHooksCallback: null, )); } @@ -6476,610 +5951,13 @@ typedef $$AudioPlayerStateTableTableProcessedTableManager $$AudioPlayerStateTableTableAnnotationComposer, $$AudioPlayerStateTableTableCreateCompanionBuilder, $$AudioPlayerStateTableTableUpdateCompanionBuilder, - (AudioPlayerStateTableData, $$AudioPlayerStateTableTableReferences), + ( + AudioPlayerStateTableData, + BaseReferences<_$AppDatabase, $AudioPlayerStateTableTable, + AudioPlayerStateTableData> + ), AudioPlayerStateTableData, - PrefetchHooks Function({bool playlistTableRefs})>; -typedef $$PlaylistTableTableCreateCompanionBuilder = PlaylistTableCompanion - Function({ - Value id, - required int audioPlayerStateId, - required int index, -}); -typedef $$PlaylistTableTableUpdateCompanionBuilder = PlaylistTableCompanion - Function({ - Value id, - Value audioPlayerStateId, - Value index, -}); - -final class $$PlaylistTableTableReferences extends BaseReferences<_$AppDatabase, - $PlaylistTableTable, PlaylistTableData> { - $$PlaylistTableTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static $AudioPlayerStateTableTable _audioPlayerStateIdTable( - _$AppDatabase db) => - db.audioPlayerStateTable.createAlias($_aliasNameGenerator( - db.playlistTable.audioPlayerStateId, db.audioPlayerStateTable.id)); - - $$AudioPlayerStateTableTableProcessedTableManager get audioPlayerStateId { - final manager = $$AudioPlayerStateTableTableTableManager( - $_db, $_db.audioPlayerStateTable) - .filter((f) => f.id($_item.audioPlayerStateId!)); - final item = $_typedResult.readTableOrNull(_audioPlayerStateIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); - } - - static MultiTypedResultKey<$PlaylistMediaTableTable, - List> _playlistMediaTableRefsTable( - _$AppDatabase db) => - MultiTypedResultKey.fromTable(db.playlistMediaTable, - aliasName: $_aliasNameGenerator( - db.playlistTable.id, db.playlistMediaTable.playlistId)); - - $$PlaylistMediaTableTableProcessedTableManager get playlistMediaTableRefs { - final manager = - $$PlaylistMediaTableTableTableManager($_db, $_db.playlistMediaTable) - .filter((f) => f.playlistId.id($_item.id)); - - final cache = - $_typedResult.readTableOrNull(_playlistMediaTableRefsTable($_db)); - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: cache)); - } -} - -class $$PlaylistTableTableFilterComposer - extends Composer<_$AppDatabase, $PlaylistTableTable> { - $$PlaylistTableTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); - - ColumnFilters get index => $composableBuilder( - column: $table.index, builder: (column) => ColumnFilters(column)); - - $$AudioPlayerStateTableTableFilterComposer get audioPlayerStateId { - final $$AudioPlayerStateTableTableFilterComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.audioPlayerStateId, - referencedTable: $db.audioPlayerStateTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$AudioPlayerStateTableTableFilterComposer( - $db: $db, - $table: $db.audioPlayerStateTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } - - Expression playlistMediaTableRefs( - Expression Function($$PlaylistMediaTableTableFilterComposer f) f) { - final $$PlaylistMediaTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playlistMediaTable, - getReferencedColumn: (t) => t.playlistId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistMediaTableTableFilterComposer( - $db: $db, - $table: $db.playlistMediaTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return f(composer); - } -} - -class $$PlaylistTableTableOrderingComposer - extends Composer<_$AppDatabase, $PlaylistTableTable> { - $$PlaylistTableTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get index => $composableBuilder( - column: $table.index, builder: (column) => ColumnOrderings(column)); - - $$AudioPlayerStateTableTableOrderingComposer get audioPlayerStateId { - final $$AudioPlayerStateTableTableOrderingComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.audioPlayerStateId, - referencedTable: $db.audioPlayerStateTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$AudioPlayerStateTableTableOrderingComposer( - $db: $db, - $table: $db.audioPlayerStateTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } -} - -class $$PlaylistTableTableAnnotationComposer - extends Composer<_$AppDatabase, $PlaylistTableTable> { - $$PlaylistTableTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn get index => - $composableBuilder(column: $table.index, builder: (column) => column); - - $$AudioPlayerStateTableTableAnnotationComposer get audioPlayerStateId { - final $$AudioPlayerStateTableTableAnnotationComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.audioPlayerStateId, - referencedTable: $db.audioPlayerStateTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$AudioPlayerStateTableTableAnnotationComposer( - $db: $db, - $table: $db.audioPlayerStateTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } - - Expression playlistMediaTableRefs( - Expression Function($$PlaylistMediaTableTableAnnotationComposer a) f) { - final $$PlaylistMediaTableTableAnnotationComposer composer = - $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.id, - referencedTable: $db.playlistMediaTable, - getReferencedColumn: (t) => t.playlistId, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistMediaTableTableAnnotationComposer( - $db: $db, - $table: $db.playlistMediaTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return f(composer); - } -} - -class $$PlaylistTableTableTableManager extends RootTableManager< - _$AppDatabase, - $PlaylistTableTable, - PlaylistTableData, - $$PlaylistTableTableFilterComposer, - $$PlaylistTableTableOrderingComposer, - $$PlaylistTableTableAnnotationComposer, - $$PlaylistTableTableCreateCompanionBuilder, - $$PlaylistTableTableUpdateCompanionBuilder, - (PlaylistTableData, $$PlaylistTableTableReferences), - PlaylistTableData, - PrefetchHooks Function( - {bool audioPlayerStateId, bool playlistMediaTableRefs})> { - $$PlaylistTableTableTableManager(_$AppDatabase db, $PlaylistTableTable table) - : super(TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$PlaylistTableTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PlaylistTableTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PlaylistTableTableAnnotationComposer($db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value audioPlayerStateId = const Value.absent(), - Value index = const Value.absent(), - }) => - PlaylistTableCompanion( - id: id, - audioPlayerStateId: audioPlayerStateId, - index: index, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - required int audioPlayerStateId, - required int index, - }) => - PlaylistTableCompanion.insert( - id: id, - audioPlayerStateId: audioPlayerStateId, - index: index, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PlaylistTableTableReferences(db, table, e) - )) - .toList(), - prefetchHooksCallback: ( - {audioPlayerStateId = false, playlistMediaTableRefs = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [ - if (playlistMediaTableRefs) db.playlistMediaTable - ], - addJoins: < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic>>(state) { - if (audioPlayerStateId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.audioPlayerStateId, - referencedTable: $$PlaylistTableTableReferences - ._audioPlayerStateIdTable(db), - referencedColumn: $$PlaylistTableTableReferences - ._audioPlayerStateIdTable(db) - .id, - ) as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return [ - if (playlistMediaTableRefs) - await $_getPrefetchedData( - currentTable: table, - referencedTable: $$PlaylistTableTableReferences - ._playlistMediaTableRefsTable(db), - managerFromTypedResult: (p0) => - $$PlaylistTableTableReferences(db, table, p0) - .playlistMediaTableRefs, - referencedItemsForCurrentItem: - (item, referencedItems) => referencedItems - .where((e) => e.playlistId == item.id), - typedResults: items) - ]; - }, - ); - }, - )); -} - -typedef $$PlaylistTableTableProcessedTableManager = ProcessedTableManager< - _$AppDatabase, - $PlaylistTableTable, - PlaylistTableData, - $$PlaylistTableTableFilterComposer, - $$PlaylistTableTableOrderingComposer, - $$PlaylistTableTableAnnotationComposer, - $$PlaylistTableTableCreateCompanionBuilder, - $$PlaylistTableTableUpdateCompanionBuilder, - (PlaylistTableData, $$PlaylistTableTableReferences), - PlaylistTableData, - PrefetchHooks Function( - {bool audioPlayerStateId, bool playlistMediaTableRefs})>; -typedef $$PlaylistMediaTableTableCreateCompanionBuilder - = PlaylistMediaTableCompanion Function({ - Value id, - required int playlistId, - required String uri, - Value?> extras, - Value?> httpHeaders, -}); -typedef $$PlaylistMediaTableTableUpdateCompanionBuilder - = PlaylistMediaTableCompanion Function({ - Value id, - Value playlistId, - Value uri, - Value?> extras, - Value?> httpHeaders, -}); - -final class $$PlaylistMediaTableTableReferences extends BaseReferences< - _$AppDatabase, $PlaylistMediaTableTable, PlaylistMediaTableData> { - $$PlaylistMediaTableTableReferences( - super.$_db, super.$_table, super.$_typedResult); - - static $PlaylistTableTable _playlistIdTable(_$AppDatabase db) => - db.playlistTable.createAlias($_aliasNameGenerator( - db.playlistMediaTable.playlistId, db.playlistTable.id)); - - $$PlaylistTableTableProcessedTableManager get playlistId { - final manager = $$PlaylistTableTableTableManager($_db, $_db.playlistTable) - .filter((f) => f.id($_item.playlistId!)); - final item = $_typedResult.readTableOrNull(_playlistIdTable($_db)); - if (item == null) return manager; - return ProcessedTableManager( - manager.$state.copyWith(prefetchedData: [item])); - } -} - -class $$PlaylistMediaTableTableFilterComposer - extends Composer<_$AppDatabase, $PlaylistMediaTableTable> { - $$PlaylistMediaTableTableFilterComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnFilters get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnFilters(column)); - - ColumnFilters get uri => $composableBuilder( - column: $table.uri, builder: (column) => ColumnFilters(column)); - - ColumnWithTypeConverterFilters?, Map, - String> - get extras => $composableBuilder( - column: $table.extras, - builder: (column) => ColumnWithTypeConverterFilters(column)); - - ColumnWithTypeConverterFilters?, Map, - String> - get httpHeaders => $composableBuilder( - column: $table.httpHeaders, - builder: (column) => ColumnWithTypeConverterFilters(column)); - - $$PlaylistTableTableFilterComposer get playlistId { - final $$PlaylistTableTableFilterComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.playlistId, - referencedTable: $db.playlistTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistTableTableFilterComposer( - $db: $db, - $table: $db.playlistTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } -} - -class $$PlaylistMediaTableTableOrderingComposer - extends Composer<_$AppDatabase, $PlaylistMediaTableTable> { - $$PlaylistMediaTableTableOrderingComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - ColumnOrderings get id => $composableBuilder( - column: $table.id, builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get uri => $composableBuilder( - column: $table.uri, builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get extras => $composableBuilder( - column: $table.extras, builder: (column) => ColumnOrderings(column)); - - ColumnOrderings get httpHeaders => $composableBuilder( - column: $table.httpHeaders, builder: (column) => ColumnOrderings(column)); - - $$PlaylistTableTableOrderingComposer get playlistId { - final $$PlaylistTableTableOrderingComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.playlistId, - referencedTable: $db.playlistTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistTableTableOrderingComposer( - $db: $db, - $table: $db.playlistTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } -} - -class $$PlaylistMediaTableTableAnnotationComposer - extends Composer<_$AppDatabase, $PlaylistMediaTableTable> { - $$PlaylistMediaTableTableAnnotationComposer({ - required super.$db, - required super.$table, - super.joinBuilder, - super.$addJoinBuilderToRootComposer, - super.$removeJoinBuilderFromRootComposer, - }); - GeneratedColumn get id => - $composableBuilder(column: $table.id, builder: (column) => column); - - GeneratedColumn get uri => - $composableBuilder(column: $table.uri, builder: (column) => column); - - GeneratedColumnWithTypeConverter?, String> get extras => - $composableBuilder(column: $table.extras, builder: (column) => column); - - GeneratedColumnWithTypeConverter?, String> - get httpHeaders => $composableBuilder( - column: $table.httpHeaders, builder: (column) => column); - - $$PlaylistTableTableAnnotationComposer get playlistId { - final $$PlaylistTableTableAnnotationComposer composer = $composerBuilder( - composer: this, - getCurrentColumn: (t) => t.playlistId, - referencedTable: $db.playlistTable, - getReferencedColumn: (t) => t.id, - builder: (joinBuilder, - {$addJoinBuilderToRootComposer, - $removeJoinBuilderFromRootComposer}) => - $$PlaylistTableTableAnnotationComposer( - $db: $db, - $table: $db.playlistTable, - $addJoinBuilderToRootComposer: $addJoinBuilderToRootComposer, - joinBuilder: joinBuilder, - $removeJoinBuilderFromRootComposer: - $removeJoinBuilderFromRootComposer, - )); - return composer; - } -} - -class $$PlaylistMediaTableTableTableManager extends RootTableManager< - _$AppDatabase, - $PlaylistMediaTableTable, - PlaylistMediaTableData, - $$PlaylistMediaTableTableFilterComposer, - $$PlaylistMediaTableTableOrderingComposer, - $$PlaylistMediaTableTableAnnotationComposer, - $$PlaylistMediaTableTableCreateCompanionBuilder, - $$PlaylistMediaTableTableUpdateCompanionBuilder, - (PlaylistMediaTableData, $$PlaylistMediaTableTableReferences), - PlaylistMediaTableData, - PrefetchHooks Function({bool playlistId})> { - $$PlaylistMediaTableTableTableManager( - _$AppDatabase db, $PlaylistMediaTableTable table) - : super(TableManagerState( - db: db, - table: table, - createFilteringComposer: () => - $$PlaylistMediaTableTableFilterComposer($db: db, $table: table), - createOrderingComposer: () => - $$PlaylistMediaTableTableOrderingComposer($db: db, $table: table), - createComputedFieldComposer: () => - $$PlaylistMediaTableTableAnnotationComposer( - $db: db, $table: table), - updateCompanionCallback: ({ - Value id = const Value.absent(), - Value playlistId = const Value.absent(), - Value uri = const Value.absent(), - Value?> extras = const Value.absent(), - Value?> httpHeaders = const Value.absent(), - }) => - PlaylistMediaTableCompanion( - id: id, - playlistId: playlistId, - uri: uri, - extras: extras, - httpHeaders: httpHeaders, - ), - createCompanionCallback: ({ - Value id = const Value.absent(), - required int playlistId, - required String uri, - Value?> extras = const Value.absent(), - Value?> httpHeaders = const Value.absent(), - }) => - PlaylistMediaTableCompanion.insert( - id: id, - playlistId: playlistId, - uri: uri, - extras: extras, - httpHeaders: httpHeaders, - ), - withReferenceMapper: (p0) => p0 - .map((e) => ( - e.readTable(table), - $$PlaylistMediaTableTableReferences(db, table, e) - )) - .toList(), - prefetchHooksCallback: ({playlistId = false}) { - return PrefetchHooks( - db: db, - explicitlyWatchedTables: [], - addJoins: < - T extends TableManagerState< - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic, - dynamic>>(state) { - if (playlistId) { - state = state.withJoin( - currentTable: table, - currentColumn: table.playlistId, - referencedTable: $$PlaylistMediaTableTableReferences - ._playlistIdTable(db), - referencedColumn: $$PlaylistMediaTableTableReferences - ._playlistIdTable(db) - .id, - ) as T; - } - - return state; - }, - getPrefetchedDataCallback: (items) async { - return []; - }, - ); - }, - )); -} - -typedef $$PlaylistMediaTableTableProcessedTableManager = ProcessedTableManager< - _$AppDatabase, - $PlaylistMediaTableTable, - PlaylistMediaTableData, - $$PlaylistMediaTableTableFilterComposer, - $$PlaylistMediaTableTableOrderingComposer, - $$PlaylistMediaTableTableAnnotationComposer, - $$PlaylistMediaTableTableCreateCompanionBuilder, - $$PlaylistMediaTableTableUpdateCompanionBuilder, - (PlaylistMediaTableData, $$PlaylistMediaTableTableReferences), - PlaylistMediaTableData, - PrefetchHooks Function({bool playlistId})>; + PrefetchHooks Function()>; typedef $$HistoryTableTableCreateCompanionBuilder = HistoryTableCompanion Function({ Value id, @@ -7646,10 +6524,6 @@ class $AppDatabaseManager { $$SourceMatchTableTableTableManager(_db, _db.sourceMatchTable); $$AudioPlayerStateTableTableTableManager get audioPlayerStateTable => $$AudioPlayerStateTableTableTableManager(_db, _db.audioPlayerStateTable); - $$PlaylistTableTableTableManager get playlistTable => - $$PlaylistTableTableTableManager(_db, _db.playlistTable); - $$PlaylistMediaTableTableTableManager get playlistMediaTable => - $$PlaylistMediaTableTableTableManager(_db, _db.playlistMediaTable); $$HistoryTableTableTableManager get historyTable => $$HistoryTableTableTableManager(_db, _db.historyTable); $$LyricsTableTableTableManager get lyricsTable => diff --git a/lib/models/database/tables/audio_player_state.dart b/lib/models/database/tables/audio_player_state.dart index 3e49cf6f..7cb62709 100644 --- a/lib/models/database/tables/audio_player_state.dart +++ b/lib/models/database/tables/audio_player_state.dart @@ -6,22 +6,30 @@ class AudioPlayerStateTable extends Table { TextColumn get loopMode => textEnum()(); BoolColumn get shuffled => boolean()(); TextColumn get collections => text().map(const StringListConverter())(); + TextColumn get tracks => + text().map(const SpotubeTrackObjectListConverter())(); + IntColumn get currentIndex => integer()(); } -class PlaylistTable extends Table { - IntColumn get id => integer().autoIncrement()(); - IntColumn get audioPlayerStateId => - integer().references(AudioPlayerStateTable, #id)(); - IntColumn get index => integer()(); -} +class SpotubeTrackObjectListConverter + extends TypeConverter, String> { + const SpotubeTrackObjectListConverter(); -class PlaylistMediaTable extends Table { - IntColumn get id => integer().autoIncrement()(); - IntColumn get playlistId => integer().references(PlaylistTable, #id)(); + @override + List fromSql(String fromDb) { + return fromDb + .split(",") + .where((e) => e.isNotEmpty) + .map( + (e) => SpotubeTrackObject.fromJson( + json.decode(e) as Map, + ), + ) + .toList(); + } - TextColumn get uri => text()(); - TextColumn get extras => - text().nullable().map(const MapTypeConverter())(); - TextColumn get httpHeaders => - text().nullable().map(const MapTypeConverter())(); + @override + String toSql(List value) { + return value.map((e) => json.encode(e)).join(","); + } } diff --git a/lib/models/database/typeconverters/map_list.dart b/lib/models/database/typeconverters/map_list.dart new file mode 100644 index 00000000..b92e781d --- /dev/null +++ b/lib/models/database/typeconverters/map_list.dart @@ -0,0 +1,20 @@ +part of '../database.dart'; + +class MapListConverter + extends TypeConverter>, String> { + const MapListConverter(); + + @override + List> fromSql(String fromDb) { + return fromDb + .split(",") + .where((e) => e.isNotEmpty) + .map((e) => json.decode(e) as Map) + .toList(); + } + + @override + String toSql(List> value) { + return value.map((e) => json.encode(e)).join(","); + } +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index c3ce0436..d7aebdb3 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -1,8 +1,13 @@ library metadata_objects; +import 'dart:typed_data'; + import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:media_kit/media_kit.dart'; +import 'package:metadata_god/metadata_god.dart'; import 'package:spotube/collections/assets.gen.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/primitive_utils.dart'; part 'metadata.g.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index d1fa4f45..45ce4e84 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -2822,6 +2822,8 @@ abstract class _SpotubeSearchResponseObject SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map json) { switch (json['runtimeType']) { + case 'local': + return SpotubeLocalTrackObject.fromJson(json); case 'full': return SpotubeFullTrackObject.fromJson(json); case 'simple': @@ -2842,9 +2844,17 @@ mixin _$SpotubeTrackObject { throw _privateConstructorUsedError; SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError; int get durationMs => throw _privateConstructorUsedError; - bool get explicit => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path) + local, required TResult Function( String id, String name, @@ -2868,6 +2878,15 @@ mixin _$SpotubeTrackObject { throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult? Function( String id, String name, @@ -2891,6 +2910,15 @@ mixin _$SpotubeTrackObject { throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult Function( String id, String name, @@ -2915,18 +2943,21 @@ mixin _$SpotubeTrackObject { throw _privateConstructorUsedError; @optionalTypeArgs TResult map({ + required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, required TResult Function(SpotubeSimpleTrackObject value) simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ + TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, TResult? Function(SpotubeSimpleTrackObject value)? simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ + TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), @@ -2955,8 +2986,7 @@ abstract class $SpotubeTrackObjectCopyWith<$Res> { String externalUri, List artists, SpotubeSimpleAlbumObject album, - int durationMs, - bool explicit}); + int durationMs}); $SpotubeSimpleAlbumObjectCopyWith<$Res>? get album; } @@ -2982,7 +3012,6 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> Object? artists = null, Object? album = null, Object? durationMs = null, - Object? explicit = null, }) { return _then(_value.copyWith( id: null == id @@ -3009,10 +3038,6 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> ? _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, ) as $Val); } @@ -3031,6 +3056,358 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> } } +/// @nodoc +abstract class _$$SpotubeLocalTrackObjectImplCopyWith<$Res> + implements $SpotubeTrackObjectCopyWith<$Res> { + factory _$$SpotubeLocalTrackObjectImplCopyWith( + _$SpotubeLocalTrackObjectImpl value, + $Res Function(_$SpotubeLocalTrackObjectImpl) then) = + __$$SpotubeLocalTrackObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path}); + + @override + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album; +} + +/// @nodoc +class __$$SpotubeLocalTrackObjectImplCopyWithImpl<$Res> + extends _$SpotubeTrackObjectCopyWithImpl<$Res, + _$SpotubeLocalTrackObjectImpl> + implements _$$SpotubeLocalTrackObjectImplCopyWith<$Res> { + __$$SpotubeLocalTrackObjectImplCopyWithImpl( + _$SpotubeLocalTrackObjectImpl _value, + $Res Function(_$SpotubeLocalTrackObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of SpotubeTrackObject + /// 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? artists = null, + Object? album = null, + Object? durationMs = null, + Object? path = null, + }) { + return _then(_$SpotubeLocalTrackObjectImpl( + 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, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + album: null == 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, + path: null == path + ? _value.path + : path // ignore: cast_nullable_to_non_nullable + as String, + )); + } + + /// 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 _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { + _$SpotubeLocalTrackObjectImpl( + {required this.id, + required this.name, + required this.externalUri, + final List artists = const [], + required this.album, + required this.durationMs, + required this.path, + final String? $type}) + : _artists = artists, + $type = $type ?? 'local'; + + factory _$SpotubeLocalTrackObjectImpl.fromJson(Map json) => + _$$SpotubeLocalTrackObjectImplFromJson(json); + + @override + final String id; + @override + final String name; + @override + final String externalUri; + final List _artists; + @override + @JsonKey() + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + @override + final SpotubeSimpleAlbumObject album; + @override + final int durationMs; + @override + final String path; + + @JsonKey(name: 'runtimeType') + final String $type; + + @override + String toString() { + return 'SpotubeTrackObject.local(id: $id, name: $name, externalUri: $externalUri, artists: $artists, album: $album, durationMs: $durationMs, path: $path)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$SpotubeLocalTrackObjectImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.name, name) || other.name == name) && + (identical(other.externalUri, externalUri) || + other.externalUri == externalUri) && + const DeepCollectionEquality().equals(other._artists, _artists) && + (identical(other.album, album) || other.album == album) && + (identical(other.durationMs, durationMs) || + other.durationMs == durationMs) && + (identical(other.path, path) || other.path == path)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, id, name, externalUri, + const DeepCollectionEquality().hash(_artists), album, durationMs, path); + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> + get copyWith => __$$SpotubeLocalTrackObjectImplCopyWithImpl< + _$SpotubeLocalTrackObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path) + local, + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit) + full, + required TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album) + simple, + }) { + return local(id, name, externalUri, artists, album, durationMs, path); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult? Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + }) { + return local?.call(id, name, externalUri, artists, album, durationMs, path); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String isrc, + bool explicit)? + full, + TResult Function( + String id, + String name, + String externalUri, + int durationMs, + bool explicit, + List artists, + SpotubeSimpleAlbumObject? album)? + simple, + required TResult orElse(), + }) { + if (local != null) { + return local(id, name, externalUri, artists, album, durationMs, path); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(SpotubeLocalTrackObject value) local, + required TResult Function(SpotubeFullTrackObject value) full, + required TResult Function(SpotubeSimpleTrackObject value) simple, + }) { + return local(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(SpotubeLocalTrackObject value)? local, + TResult? Function(SpotubeFullTrackObject value)? full, + TResult? Function(SpotubeSimpleTrackObject value)? simple, + }) { + return local?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(SpotubeLocalTrackObject value)? local, + TResult Function(SpotubeFullTrackObject value)? full, + TResult Function(SpotubeSimpleTrackObject value)? simple, + required TResult orElse(), + }) { + if (local != null) { + return local(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$SpotubeLocalTrackObjectImplToJson( + this, + ); + } +} + +abstract class SpotubeLocalTrackObject implements SpotubeTrackObject { + factory SpotubeLocalTrackObject( + {required final String id, + required final String name, + required final String externalUri, + final List artists, + required final SpotubeSimpleAlbumObject album, + required final int durationMs, + required final String path}) = _$SpotubeLocalTrackObjectImpl; + + factory SpotubeLocalTrackObject.fromJson(Map json) = + _$SpotubeLocalTrackObjectImpl.fromJson; + + @override + String get id; + @override + String get name; + @override + String get externalUri; + @override + List get artists; + @override + SpotubeSimpleAlbumObject get album; + @override + int get durationMs; + String get path; + + /// Create a copy of SpotubeTrackObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$SpotubeLocalTrackObjectImplCopyWith<_$SpotubeLocalTrackObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + /// @nodoc abstract class _$$SpotubeFullTrackObjectImplCopyWith<$Res> implements $SpotubeTrackObjectCopyWith<$Res> { @@ -3218,6 +3595,15 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult when({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path) + local, required TResult Function( String id, String name, @@ -3245,6 +3631,15 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult? Function( String id, String name, @@ -3272,6 +3667,15 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult Function( String id, String name, @@ -3303,6 +3707,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult map({ + required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, required TResult Function(SpotubeSimpleTrackObject value) simple, }) { @@ -3312,6 +3717,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult? mapOrNull({ + TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, TResult? Function(SpotubeSimpleTrackObject value)? simple, }) { @@ -3321,6 +3727,7 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { @override @optionalTypeArgs TResult maybeMap({ + TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), @@ -3366,7 +3773,6 @@ abstract class SpotubeFullTrackObject implements SpotubeTrackObject { @override int get durationMs; String get isrc; - @override bool get explicit; /// Create a copy of SpotubeTrackObject @@ -3544,6 +3950,15 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult when({ + required TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path) + local, required TResult Function( String id, String name, @@ -3570,6 +3985,15 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult? whenOrNull({ + TResult? Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult? Function( String id, String name, @@ -3597,6 +4021,15 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult maybeWhen({ + TResult Function( + String id, + String name, + String externalUri, + List artists, + SpotubeSimpleAlbumObject album, + int durationMs, + String path)? + local, TResult Function( String id, String name, @@ -3628,6 +4061,7 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult map({ + required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, required TResult Function(SpotubeSimpleTrackObject value) simple, }) { @@ -3637,6 +4071,7 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult? mapOrNull({ + TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, TResult? Function(SpotubeSimpleTrackObject value)? simple, }) { @@ -3646,6 +4081,7 @@ class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { @override @optionalTypeArgs TResult maybeMap({ + TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), @@ -3685,7 +4121,6 @@ abstract class SpotubeSimpleTrackObject implements SpotubeTrackObject { String get externalUri; @override int get durationMs; - @override bool get explicit; @override List get artists; diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 887df6d2..3303e324 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -287,6 +287,37 @@ Map _$$SpotubeSearchResponseObjectImplToJson( 'tracks': instance.tracks.map((e) => e.toJson()).toList(), }; +_$SpotubeLocalTrackObjectImpl _$$SpotubeLocalTrackObjectImplFromJson( + Map json) => + _$SpotubeLocalTrackObjectImpl( + id: json['id'] as String, + name: json['name'] as String, + externalUri: json['externalUri'] as String, + artists: (json['artists'] as List?) + ?.map((e) => SpotubeSimpleArtistObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + album: SpotubeSimpleAlbumObject.fromJson( + Map.from(json['album'] as Map)), + durationMs: (json['durationMs'] as num).toInt(), + path: json['path'] as String, + $type: json['runtimeType'] as String?, + ); + +Map _$$SpotubeLocalTrackObjectImplToJson( + _$SpotubeLocalTrackObjectImpl instance) => + { + 'id': instance.id, + 'name': instance.name, + 'externalUri': instance.externalUri, + 'artists': instance.artists.map((e) => e.toJson()).toList(), + 'album': instance.album.toJson(), + 'durationMs': instance.durationMs, + 'path': instance.path, + 'runtimeType': instance.$type, + }; + _$SpotubeFullTrackObjectImpl _$$SpotubeFullTrackObjectImplFromJson(Map json) => _$SpotubeFullTrackObjectImpl( id: json['id'] as String, diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index eb82f2d8..b7cf1a3e 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -2,6 +2,16 @@ part of 'metadata.dart'; @freezed class SpotubeTrackObject with _$SpotubeTrackObject { + factory SpotubeTrackObject.local({ + required String id, + required String name, + required String externalUri, + @Default([]) List artists, + required SpotubeSimpleAlbumObject album, + required int durationMs, + required String path, + }) = SpotubeLocalTrackObject; + factory SpotubeTrackObject.full({ required String id, required String name, @@ -27,6 +37,40 @@ class SpotubeTrackObject with _$SpotubeTrackObject { _$SpotubeTrackObjectFromJson( json.containsKey("isrc") ? {...json, "runtimeType": "full"} - : {...json, "runtimeType": "simple"}, + : json.containsKey("path") + ? {...json, "runtimeType": "local"} + : {...json, "runtimeType": "simple"}, ); } + +extension AsMediaListSpotubeTrackObject on Iterable { + List asMediaList() { + return map((track) => SpotubeMedia(track)).toList(); + } +} + +extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject { + Metadata toMetadata({ + required int fileLength, + Uint8List? imageBytes, + }) { + return Metadata( + title: name, + artist: artists.map((a) => a.name).join(", "), + album: album.name, + albumArtist: artists.map((a) => a.name).join(", "), + year: album.releaseDate == null + ? 1970 + : DateTime.parse(album.releaseDate!).year, + durationMs: durationMs.toDouble(), + fileSize: BigInt.from(fileLength), + picture: imageBytes != null + ? Picture( + data: imageBytes, + // Spotify images are always JPEGs + mimeType: 'image/jpeg', + ) + : null, + ); + } +} diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart new file mode 100644 index 00000000..d894ff8b --- /dev/null +++ b/lib/models/playback/track_sources.dart @@ -0,0 +1,94 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/services/sourced_track/enums.dart'; + +part 'track_sources.freezed.dart'; +part 'track_sources.g.dart'; + +@freezed +class TrackSourceQuery with _$TrackSourceQuery { + factory TrackSourceQuery({ + required String id, + required String title, + required List artists, + required String album, + required int durationMs, + required String isrc, + required bool explicit, + }) = _TrackSourceQuery; + + factory TrackSourceQuery.fromJson(Map json) => + _$TrackSourceQueryFromJson(json); + + factory TrackSourceQuery.fromTrack(SpotubeFullTrackObject track) { + return TrackSourceQuery( + id: track.id, + title: track.name, + artists: track.artists.map((e) => e.name).toList(), + album: track.album.name, + durationMs: track.durationMs, + isrc: track.isrc, + explicit: track.explicit, + ); + } + + /// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery]. + factory TrackSourceQuery.parseUri(String url) { + final uri = Uri.parse(url); + return TrackSourceQuery.fromJson({ + "id": uri.pathSegments.last, + ...uri.queryParameters, + }); + } +} + +@freezed +class TrackSourceInfo with _$TrackSourceInfo { + factory TrackSourceInfo({ + required String id, + required String title, + required String artists, + required String thumbnail, + required String pageUrl, + required int durationMs, + }) = _TrackSourceInfo; + + factory TrackSourceInfo.fromJson(Map json) => + _$TrackSourceInfoFromJson(json); +} + +@freezed +class TrackSource with _$TrackSource { + factory TrackSource({ + required String url, + required SourceQualities quality, + required SourceCodecs codec, + required String bitrate, + }) = _TrackSource; + + factory TrackSource.fromJson(Map json) => + _$TrackSourceFromJson(json); +} + +@JsonSerializable() +abstract class BasicSourcedTrack { + final TrackSourceQuery query; + final AudioSource source; + final TrackSourceInfo info; + final List sources; + @JsonKey(defaultValue: []) + final List siblings; + BasicSourcedTrack({ + required this.query, + required this.source, + required this.info, + required this.sources, + this.siblings = const [], + }); + + factory BasicSourcedTrack.fromJson(Map json) => + _$BasicSourcedTrackFromJson(json); + Map toJson() => _$BasicSourcedTrackToJson(this); +} diff --git a/lib/models/playback/track_sources.freezed.dart b/lib/models/playback/track_sources.freezed.dart new file mode 100644 index 00000000..1f78d2b3 --- /dev/null +++ b/lib/models/playback/track_sources.freezed.dart @@ -0,0 +1,776 @@ +// 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 'track_sources.dart'; + +// ************************************************************************** +// FreezedGenerator +// ************************************************************************** + +T _$identity(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'); + +TrackSourceQuery _$TrackSourceQueryFromJson(Map json) { + return _TrackSourceQuery.fromJson(json); +} + +/// @nodoc +mixin _$TrackSourceQuery { + String get id => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + List get artists => throw _privateConstructorUsedError; + String get album => throw _privateConstructorUsedError; + int get durationMs => throw _privateConstructorUsedError; + String get isrc => throw _privateConstructorUsedError; + bool get explicit => throw _privateConstructorUsedError; + + /// Serializes this TrackSourceQuery to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of TrackSourceQuery + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrackSourceQueryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrackSourceQueryCopyWith<$Res> { + factory $TrackSourceQueryCopyWith( + TrackSourceQuery value, $Res Function(TrackSourceQuery) then) = + _$TrackSourceQueryCopyWithImpl<$Res, TrackSourceQuery>; + @useResult + $Res call( + {String id, + String title, + List artists, + String album, + int durationMs, + String isrc, + bool explicit}); +} + +/// @nodoc +class _$TrackSourceQueryCopyWithImpl<$Res, $Val extends TrackSourceQuery> + implements $TrackSourceQueryCopyWith<$Res> { + _$TrackSourceQueryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrackSourceQuery + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? album = null, + Object? durationMs = null, + Object? isrc = null, + Object? explicit = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + album: null == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as String, + 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 + as bool, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TrackSourceQueryImplCopyWith<$Res> + implements $TrackSourceQueryCopyWith<$Res> { + factory _$$TrackSourceQueryImplCopyWith(_$TrackSourceQueryImpl value, + $Res Function(_$TrackSourceQueryImpl) then) = + __$$TrackSourceQueryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + List artists, + String album, + int durationMs, + String isrc, + bool explicit}); +} + +/// @nodoc +class __$$TrackSourceQueryImplCopyWithImpl<$Res> + extends _$TrackSourceQueryCopyWithImpl<$Res, _$TrackSourceQueryImpl> + implements _$$TrackSourceQueryImplCopyWith<$Res> { + __$$TrackSourceQueryImplCopyWithImpl(_$TrackSourceQueryImpl _value, + $Res Function(_$TrackSourceQueryImpl) _then) + : super(_value, _then); + + /// Create a copy of TrackSourceQuery + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? album = null, + Object? durationMs = null, + Object? isrc = null, + Object? explicit = null, + }) { + return _then(_$TrackSourceQueryImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value._artists + : artists // ignore: cast_nullable_to_non_nullable + as List, + album: null == album + ? _value.album + : album // ignore: cast_nullable_to_non_nullable + as String, + 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 + as bool, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TrackSourceQueryImpl implements _TrackSourceQuery { + _$TrackSourceQueryImpl( + {required this.id, + required this.title, + required final List artists, + required this.album, + required this.durationMs, + required this.isrc, + required this.explicit}) + : _artists = artists; + + factory _$TrackSourceQueryImpl.fromJson(Map json) => + _$$TrackSourceQueryImplFromJson(json); + + @override + final String id; + @override + final String title; + final List _artists; + @override + List get artists { + if (_artists is EqualUnmodifiableListView) return _artists; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_artists); + } + + @override + final String album; + @override + final int durationMs; + @override + final String isrc; + @override + final bool explicit; + + @override + String toString() { + return 'TrackSourceQuery(id: $id, title: $title, artists: $artists, album: $album, durationMs: $durationMs, isrc: $isrc, explicit: $explicit)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrackSourceQueryImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + const DeepCollectionEquality().equals(other._artists, _artists) && + (identical(other.album, album) || other.album == album) && + (identical(other.durationMs, durationMs) || + other.durationMs == durationMs) && + (identical(other.isrc, isrc) || other.isrc == isrc) && + (identical(other.explicit, explicit) || + other.explicit == explicit)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + id, + title, + const DeepCollectionEquality().hash(_artists), + album, + durationMs, + isrc, + explicit); + + /// Create a copy of TrackSourceQuery + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => + __$$TrackSourceQueryImplCopyWithImpl<_$TrackSourceQueryImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$TrackSourceQueryImplToJson( + this, + ); + } +} + +abstract class _TrackSourceQuery implements TrackSourceQuery { + factory _TrackSourceQuery( + {required final String id, + required final String title, + required final List artists, + required final String album, + required final int durationMs, + required final String isrc, + required final bool explicit}) = _$TrackSourceQueryImpl; + + factory _TrackSourceQuery.fromJson(Map json) = + _$TrackSourceQueryImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + List get artists; + @override + String get album; + @override + int get durationMs; + @override + String get isrc; + @override + bool get explicit; + + /// Create a copy of TrackSourceQuery + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => + throw _privateConstructorUsedError; +} + +TrackSourceInfo _$TrackSourceInfoFromJson(Map json) { + return _TrackSourceInfo.fromJson(json); +} + +/// @nodoc +mixin _$TrackSourceInfo { + String get id => throw _privateConstructorUsedError; + String get title => throw _privateConstructorUsedError; + String get artists => throw _privateConstructorUsedError; + String get thumbnail => throw _privateConstructorUsedError; + String get pageUrl => throw _privateConstructorUsedError; + int get durationMs => throw _privateConstructorUsedError; + + /// Serializes this TrackSourceInfo to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of TrackSourceInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrackSourceInfoCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrackSourceInfoCopyWith<$Res> { + factory $TrackSourceInfoCopyWith( + TrackSourceInfo value, $Res Function(TrackSourceInfo) then) = + _$TrackSourceInfoCopyWithImpl<$Res, TrackSourceInfo>; + @useResult + $Res call( + {String id, + String title, + String artists, + String thumbnail, + String pageUrl, + int durationMs}); +} + +/// @nodoc +class _$TrackSourceInfoCopyWithImpl<$Res, $Val extends TrackSourceInfo> + implements $TrackSourceInfoCopyWith<$Res> { + _$TrackSourceInfoCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrackSourceInfo + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? thumbnail = null, + Object? pageUrl = null, + Object? durationMs = null, + }) { + return _then(_value.copyWith( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as String, + thumbnail: null == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as String, + pageUrl: null == pageUrl + ? _value.pageUrl + : pageUrl // ignore: cast_nullable_to_non_nullable + as String, + durationMs: null == durationMs + ? _value.durationMs + : durationMs // ignore: cast_nullable_to_non_nullable + as int, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TrackSourceInfoImplCopyWith<$Res> + implements $TrackSourceInfoCopyWith<$Res> { + factory _$$TrackSourceInfoImplCopyWith(_$TrackSourceInfoImpl value, + $Res Function(_$TrackSourceInfoImpl) then) = + __$$TrackSourceInfoImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String id, + String title, + String artists, + String thumbnail, + String pageUrl, + int durationMs}); +} + +/// @nodoc +class __$$TrackSourceInfoImplCopyWithImpl<$Res> + extends _$TrackSourceInfoCopyWithImpl<$Res, _$TrackSourceInfoImpl> + implements _$$TrackSourceInfoImplCopyWith<$Res> { + __$$TrackSourceInfoImplCopyWithImpl( + _$TrackSourceInfoImpl _value, $Res Function(_$TrackSourceInfoImpl) _then) + : super(_value, _then); + + /// Create a copy of TrackSourceInfo + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? id = null, + Object? title = null, + Object? artists = null, + Object? thumbnail = null, + Object? pageUrl = null, + Object? durationMs = null, + }) { + return _then(_$TrackSourceInfoImpl( + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + title: null == title + ? _value.title + : title // ignore: cast_nullable_to_non_nullable + as String, + artists: null == artists + ? _value.artists + : artists // ignore: cast_nullable_to_non_nullable + as String, + thumbnail: null == thumbnail + ? _value.thumbnail + : thumbnail // ignore: cast_nullable_to_non_nullable + as String, + pageUrl: null == pageUrl + ? _value.pageUrl + : pageUrl // ignore: cast_nullable_to_non_nullable + as String, + durationMs: null == durationMs + ? _value.durationMs + : durationMs // ignore: cast_nullable_to_non_nullable + as int, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TrackSourceInfoImpl implements _TrackSourceInfo { + _$TrackSourceInfoImpl( + {required this.id, + required this.title, + required this.artists, + required this.thumbnail, + required this.pageUrl, + required this.durationMs}); + + factory _$TrackSourceInfoImpl.fromJson(Map json) => + _$$TrackSourceInfoImplFromJson(json); + + @override + final String id; + @override + final String title; + @override + final String artists; + @override + final String thumbnail; + @override + final String pageUrl; + @override + final int durationMs; + + @override + String toString() { + return 'TrackSourceInfo(id: $id, title: $title, artists: $artists, thumbnail: $thumbnail, pageUrl: $pageUrl, durationMs: $durationMs)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrackSourceInfoImpl && + (identical(other.id, id) || other.id == id) && + (identical(other.title, title) || other.title == title) && + (identical(other.artists, artists) || other.artists == artists) && + (identical(other.thumbnail, thumbnail) || + other.thumbnail == thumbnail) && + (identical(other.pageUrl, pageUrl) || other.pageUrl == pageUrl) && + (identical(other.durationMs, durationMs) || + other.durationMs == durationMs)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, id, title, artists, thumbnail, pageUrl, durationMs); + + /// Create a copy of TrackSourceInfo + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => + __$$TrackSourceInfoImplCopyWithImpl<_$TrackSourceInfoImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$TrackSourceInfoImplToJson( + this, + ); + } +} + +abstract class _TrackSourceInfo implements TrackSourceInfo { + factory _TrackSourceInfo( + {required final String id, + required final String title, + required final String artists, + required final String thumbnail, + required final String pageUrl, + required final int durationMs}) = _$TrackSourceInfoImpl; + + factory _TrackSourceInfo.fromJson(Map json) = + _$TrackSourceInfoImpl.fromJson; + + @override + String get id; + @override + String get title; + @override + String get artists; + @override + String get thumbnail; + @override + String get pageUrl; + @override + int get durationMs; + + /// Create a copy of TrackSourceInfo + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => + throw _privateConstructorUsedError; +} + +TrackSource _$TrackSourceFromJson(Map json) { + return _TrackSource.fromJson(json); +} + +/// @nodoc +mixin _$TrackSource { + String get url => throw _privateConstructorUsedError; + SourceQualities get quality => throw _privateConstructorUsedError; + SourceCodecs get codec => throw _privateConstructorUsedError; + String get bitrate => throw _privateConstructorUsedError; + + /// Serializes this TrackSource to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of TrackSource + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $TrackSourceCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $TrackSourceCopyWith<$Res> { + factory $TrackSourceCopyWith( + TrackSource value, $Res Function(TrackSource) then) = + _$TrackSourceCopyWithImpl<$Res, TrackSource>; + @useResult + $Res call( + {String url, + SourceQualities quality, + SourceCodecs codec, + String bitrate}); +} + +/// @nodoc +class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource> + implements $TrackSourceCopyWith<$Res> { + _$TrackSourceCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of TrackSource + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? quality = null, + Object? codec = null, + Object? bitrate = null, + }) { + return _then(_value.copyWith( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + quality: null == quality + ? _value.quality + : quality // ignore: cast_nullable_to_non_nullable + as SourceQualities, + codec: null == codec + ? _value.codec + : codec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + bitrate: null == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$TrackSourceImplCopyWith<$Res> + implements $TrackSourceCopyWith<$Res> { + factory _$$TrackSourceImplCopyWith( + _$TrackSourceImpl value, $Res Function(_$TrackSourceImpl) then) = + __$$TrackSourceImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String url, + SourceQualities quality, + SourceCodecs codec, + String bitrate}); +} + +/// @nodoc +class __$$TrackSourceImplCopyWithImpl<$Res> + extends _$TrackSourceCopyWithImpl<$Res, _$TrackSourceImpl> + implements _$$TrackSourceImplCopyWith<$Res> { + __$$TrackSourceImplCopyWithImpl( + _$TrackSourceImpl _value, $Res Function(_$TrackSourceImpl) _then) + : super(_value, _then); + + /// Create a copy of TrackSource + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? url = null, + Object? quality = null, + Object? codec = null, + Object? bitrate = null, + }) { + return _then(_$TrackSourceImpl( + url: null == url + ? _value.url + : url // ignore: cast_nullable_to_non_nullable + as String, + quality: null == quality + ? _value.quality + : quality // ignore: cast_nullable_to_non_nullable + as SourceQualities, + codec: null == codec + ? _value.codec + : codec // ignore: cast_nullable_to_non_nullable + as SourceCodecs, + bitrate: null == bitrate + ? _value.bitrate + : bitrate // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$TrackSourceImpl implements _TrackSource { + _$TrackSourceImpl( + {required this.url, + required this.quality, + required this.codec, + required this.bitrate}); + + factory _$TrackSourceImpl.fromJson(Map json) => + _$$TrackSourceImplFromJson(json); + + @override + final String url; + @override + final SourceQualities quality; + @override + final SourceCodecs codec; + @override + final String bitrate; + + @override + String toString() { + return 'TrackSource(url: $url, quality: $quality, codec: $codec, bitrate: $bitrate)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$TrackSourceImpl && + (identical(other.url, url) || other.url == url) && + (identical(other.quality, quality) || other.quality == quality) && + (identical(other.codec, codec) || other.codec == codec) && + (identical(other.bitrate, bitrate) || other.bitrate == bitrate)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, url, quality, codec, bitrate); + + /// Create a copy of TrackSource + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => + __$$TrackSourceImplCopyWithImpl<_$TrackSourceImpl>(this, _$identity); + + @override + Map toJson() { + return _$$TrackSourceImplToJson( + this, + ); + } +} + +abstract class _TrackSource implements TrackSource { + factory _TrackSource( + {required final String url, + required final SourceQualities quality, + required final SourceCodecs codec, + required final String bitrate}) = _$TrackSourceImpl; + + factory _TrackSource.fromJson(Map json) = + _$TrackSourceImpl.fromJson; + + @override + String get url; + @override + SourceQualities get quality; + @override + SourceCodecs get codec; + @override + String get bitrate; + + /// Create a copy of TrackSource + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/models/playback/track_sources.g.dart b/lib/models/playback/track_sources.g.dart new file mode 100644 index 00000000..e1a62ea6 --- /dev/null +++ b/lib/models/playback/track_sources.g.dart @@ -0,0 +1,110 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'track_sources.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +BasicSourcedTrack _$BasicSourcedTrackFromJson(Map json) => BasicSourcedTrack( + query: TrackSourceQuery.fromJson( + Map.from(json['query'] as Map)), + source: $enumDecode(_$AudioSourceEnumMap, json['source']), + info: TrackSourceInfo.fromJson( + Map.from(json['info'] as Map)), + sources: (json['sources'] as List) + .map((e) => TrackSource.fromJson(Map.from(e as Map))) + .toList(), + siblings: (json['siblings'] as List?) + ?.map((e) => + TrackSourceInfo.fromJson(Map.from(e as Map))) + .toList() ?? + [], + ); + +Map _$BasicSourcedTrackToJson(BasicSourcedTrack instance) => + { + 'query': instance.query.toJson(), + 'source': _$AudioSourceEnumMap[instance.source]!, + 'info': instance.info.toJson(), + 'sources': instance.sources.map((e) => e.toJson()).toList(), + 'siblings': instance.siblings.map((e) => e.toJson()).toList(), + }; + +const _$AudioSourceEnumMap = { + AudioSource.youtube: 'youtube', + AudioSource.piped: 'piped', + AudioSource.jiosaavn: 'jiosaavn', + AudioSource.invidious: 'invidious', +}; + +_$TrackSourceQueryImpl _$$TrackSourceQueryImplFromJson(Map json) => + _$TrackSourceQueryImpl( + id: json['id'] as String, + title: json['title'] as String, + artists: + (json['artists'] as List).map((e) => e as String).toList(), + album: json['album'] as String, + durationMs: (json['durationMs'] as num).toInt(), + isrc: json['isrc'] as String, + explicit: json['explicit'] as bool, + ); + +Map _$$TrackSourceQueryImplToJson( + _$TrackSourceQueryImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'artists': instance.artists, + 'album': instance.album, + 'durationMs': instance.durationMs, + 'isrc': instance.isrc, + 'explicit': instance.explicit, + }; + +_$TrackSourceInfoImpl _$$TrackSourceInfoImplFromJson(Map json) => + _$TrackSourceInfoImpl( + id: json['id'] as String, + title: json['title'] as String, + artists: json['artists'] as String, + thumbnail: json['thumbnail'] as String, + pageUrl: json['pageUrl'] as String, + durationMs: (json['durationMs'] as num).toInt(), + ); + +Map _$$TrackSourceInfoImplToJson( + _$TrackSourceInfoImpl instance) => + { + 'id': instance.id, + 'title': instance.title, + 'artists': instance.artists, + 'thumbnail': instance.thumbnail, + 'pageUrl': instance.pageUrl, + 'durationMs': instance.durationMs, + }; + +_$TrackSourceImpl _$$TrackSourceImplFromJson(Map json) => _$TrackSourceImpl( + url: json['url'] as String, + quality: $enumDecode(_$SourceQualitiesEnumMap, json['quality']), + codec: $enumDecode(_$SourceCodecsEnumMap, json['codec']), + bitrate: json['bitrate'] as String, + ); + +Map _$$TrackSourceImplToJson(_$TrackSourceImpl instance) => + { + 'url': instance.url, + 'quality': _$SourceQualitiesEnumMap[instance.quality]!, + 'codec': _$SourceCodecsEnumMap[instance.codec]!, + 'bitrate': instance.bitrate, + }; + +const _$SourceQualitiesEnumMap = { + SourceQualities.high: 'high', + SourceQualities.medium: 'medium', + SourceQualities.low: 'low', +}; + +const _$SourceCodecsEnumMap = { + SourceCodecs.m4a: 'm4a', + SourceCodecs.weba: 'weba', +}; diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index b02910e9..28cd5835 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -24,7 +24,7 @@ import 'package:spotube/models/local_track.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/server/active_sourced_track.dart'; +import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/utils/platform.dart'; @@ -44,7 +44,7 @@ class PlayerView extends HookConsumerWidget { Widget build(BuildContext context, ref) { final theme = Theme.of(context); final auth = ref.watch(authenticationProvider); - final sourcedCurrentTrack = ref.watch(activeSourcedTrackProvider); + final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider); final currentActiveTrack = ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); final currentTrack = sourcedCurrentTrack ?? currentActiveTrack; diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index d026cea9..472acf1b 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -19,7 +19,7 @@ import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; -import 'package:spotube/provider/server/active_sourced_track.dart'; +import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; import 'package:spotube/services/sourced_track/models/source_info.dart'; @@ -75,9 +75,9 @@ class SiblingTracksSheet extends HookConsumerWidget { final isSearching = useState(false); final searchMode = useState(preferences.searchMode); - final activeTrackNotifier = ref.watch(activeSourcedTrackProvider.notifier); + final activeTrackNotifier = ref.watch(activeTrackSourcesProvider.notifier); final activeTrack = - ref.watch(activeSourcedTrackProvider) ?? playlist.activeTrack; + ref.watch(activeTrackSourcesProvider) ?? playlist.activeTrack; final title = ServiceUtils.getTitle( activeTrack?.name ?? "", diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index aa93bd4f..1432e5e3 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -3,22 +3,38 @@ import 'dart:math'; import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:media_kit/media_kit.dart' hide Track; -import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/extensions/list.dart'; -import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/local_track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/discord_provider.dart'; -import 'package:spotube/provider/server/sourced_track.dart'; +import 'package:spotube/provider/server/track_sources.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; class AudioPlayerNotifier extends Notifier { BlackListNotifier get _blacklist => ref.read(blacklistProvider.notifier); + void _assertAllowedTracks(Iterable tracks) { + assert( + tracks.every( + (track) => + track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject, + ), + 'All tracks must be either SpotubeFullTrackObject or SpotubeLocalTrackObject', + ); + } + + void _assertAllowedTrack(SpotubeTrackObject tracks) { + assert( + tracks is SpotubeFullTrackObject || tracks is SpotubeLocalTrackObject, + 'Track must be either SpotubeFullTrackObject or SpotubeLocalTrackObject', + ); + } + Future _syncSavedState() async { final database = ref.read(databaseProvider); @@ -32,6 +48,8 @@ class AudioPlayerNotifier extends Notifier { loopMode: audioPlayer.loopMode, shuffled: audioPlayer.isShuffled, collections: [], + tracks: [], + currentIndex: 0, id: const Value(0), ), ); @@ -43,51 +61,20 @@ class AudioPlayerNotifier extends Notifier { await audioPlayer.setShuffle(playerState.shuffled); } - var playlist = - await database.select(database.playlistTable).getSingleOrNull(); - final medias = await database.select(database.playlistMediaTable).get(); + final tracks = playerState.tracks; + final currentIndex = playerState.currentIndex; - if (playlist == null) { - await database.into(database.playlistTable).insert( - PlaylistTableCompanion.insert( - audioPlayerStateId: 0, - index: audioPlayer.playlist.index, - id: const Value(0), - ), - ); - - playlist = await database.select(database.playlistTable).getSingle(); - } - - if (medias.isEmpty && audioPlayer.playlist.medias.isNotEmpty) { - await database.batch((batch) { - batch.insertAll( - database.playlistMediaTable, - [ - for (final media in audioPlayer.playlist.medias) - PlaylistMediaTableCompanion.insert( - playlistId: playlist!.id, - uri: media.uri, - extras: Value(media.extras), - httpHeaders: Value(media.httpHeaders), - ), - ], - ); - }); - } else if (medias.isNotEmpty) { + if (tracks.isEmpty && state.tracks.isNotEmpty) { + await _updatePlayerState( + AudioPlayerStateTableCompanion( + tracks: Value(state.tracks), + currentIndex: Value(currentIndex), + ), + ); + } else if (tracks.isNotEmpty) { await audioPlayer.openPlaylist( - medias - .map( - (media) => SpotubeMedia.fromMedia( - Media( - media.uri, - extras: media.extras, - httpHeaders: media.httpHeaders, - ), - ), - ) - .toList(), - initialIndex: playlist.index, + tracks.asMediaList(), + initialIndex: currentIndex, autoPlay: false, ); } @@ -109,36 +96,6 @@ class AudioPlayerNotifier extends Notifier { .write(companion); } - Future _updatePlaylist( - Playlist playlist, - ) async { - final database = ref.read(databaseProvider); - - await database.batch((batch) { - batch.update( - database.playlistTable, - PlaylistTableCompanion(index: Value(playlist.index)), - where: (tb) => tb.id.equals(0), - ); - - batch.deleteAll(database.playlistMediaTable); - - if (playlist.medias.isEmpty) return; - batch.insertAll( - database.playlistMediaTable, - [ - for (final media in playlist.medias) - PlaylistMediaTableCompanion.insert( - playlistId: 0, - uri: media.uri, - extras: Value(media.extras), - httpHeaders: Value(media.httpHeaders), - ), - ], - ); - }); - } - @override build() { final subscriptions = [ @@ -183,9 +140,25 @@ class AudioPlayerNotifier extends Notifier { }), audioPlayer.playlistStream.listen((playlist) async { try { - state = state.copyWith(playlist: playlist); + final queries = playlist.medias + .map((media) => TrackSourceQuery.parseUri(media.uri)) + .toList(); + final tracks = queries + .map((query) => state.tracks.firstWhere( + (element) => element.id == query.id, + )) + .toList(); + state = state.copyWith( + tracks: tracks, + currentIndex: playlist.index, + ); - await _updatePlaylist(playlist); + await _updatePlayerState( + AudioPlayerStateTableCompanion( + currentIndex: Value(state.currentIndex), + tracks: Value(state.tracks), + ), + ); } catch (e, stack) { AppLogger.reportError(e, stack); } @@ -203,8 +176,8 @@ class AudioPlayerNotifier extends Notifier { return AudioPlayerState( loopMode: audioPlayer.loopMode, playing: audioPlayer.isPlaying, - playlist: audioPlayer.playlist, shuffled: audioPlayer.isShuffled, + tracks: [], collections: [], ); } @@ -245,17 +218,16 @@ class AudioPlayerNotifier extends Notifier { await removeCollections([collectionId]); } - // Tracks related methods - Future addTracksAtFirst( - Iterable tracks, { + Iterable tracks, { bool allowDuplicates = false, }) async { + _assertAllowedTracks(tracks); if (state.tracks.length == 1) { return addTracks(tracks); } - tracks = _blacklist.filter(tracks).toList() as List; + tracks = _blacklist.filter(tracks).toList(); for (int i = 0; i < tracks.length; i++) { final track = tracks.elementAt(i); @@ -267,19 +239,23 @@ class AudioPlayerNotifier extends Notifier { await audioPlayer.addTrackAt( SpotubeMedia(track), - max(state.playlist.index, 0) + i + 1, + max(state.currentIndex, 0) + i + 1, ); } } - Future addTrack(Track track) async { + Future addTrack(SpotubeTrackObject track) async { + _assertAllowedTrack(track); + if (_blacklist.contains(track)) return; if (state.tracks.any((element) => _compareTracks(element, track))) return; await audioPlayer.addTrack(SpotubeMedia(track)); } - Future addTracks(Iterable tracks) async { - tracks = _blacklist.filter(tracks).toList() as List; + Future addTracks(Iterable tracks) async { + _assertAllowedTracks(tracks); + + tracks = _blacklist.filter(tracks).toList(); for (final track in tracks) { await audioPlayer.addTrack(SpotubeMedia(track)); } @@ -299,31 +275,40 @@ class AudioPlayerNotifier extends Notifier { } } - bool _compareTracks(Track a, Track b) { - if ((a is LocalTrack && b is! LocalTrack) || - (a is! LocalTrack && b is LocalTrack)) { + bool _compareTracks(SpotubeTrackObject a, SpotubeTrackObject b) { + if ((a is SpotubeLocalTrackObject && b is! SpotubeLocalTrackObject) || + (a is! SpotubeLocalTrackObject && b is SpotubeLocalTrackObject)) { return false; } - return a is LocalTrack && b is LocalTrack + return a is SpotubeLocalTrackObject && b is SpotubeLocalTrackObject ? (a).path == (b).path : a.id == b.id; } Future load( - List tracks, { + List tracks, { int initialIndex = 0, bool autoPlay = false, }) async { - final medias = (_blacklist.filter(tracks).toList() as List) + _assertAllowedTracks(tracks); + + final medias = _blacklist + .filter(tracks) + .toList() .asMediaList() - .unique((a, b) => _compareTracks(a.track, b.track)); + .unique((a, b) => a.uri == b.uri); // Giving the initial track a boost so MediaKit won't skip // because of timeout final intendedActiveTrack = medias.elementAt(initialIndex); - if (intendedActiveTrack.track is! LocalTrack) { - await ref.read(sourcedTrackProvider(intendedActiveTrack).future); + if (intendedActiveTrack.track is! SpotubeLocalTrackObject) { + await ref.read( + trackSourcesProvider( + TrackSourceQuery.fromTrack( + intendedActiveTrack.track as SpotubeFullTrackObject), + ).future, + ); } if (medias.isEmpty) return; @@ -337,7 +322,7 @@ class AudioPlayerNotifier extends Notifier { ); } - Future jumpToTrack(Track track) async { + Future jumpToTrack(SpotubeTrackObject track) async { final index = state.tracks.toList().indexWhere((element) => element.id == track.id); if (index == -1) return; diff --git a/lib/provider/audio_player/audio_player_streams.dart b/lib/provider/audio_player/audio_player_streams.dart index c221a2b0..baf7b624 100644 --- a/lib/provider/audio_player/audio_player_streams.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -2,16 +2,17 @@ import 'dart:async'; import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/local_track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/discord_provider.dart'; import 'package:spotube/provider/history/history.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/server/track_sources.dart'; import 'package:spotube/provider/skip_segments/skip_segments.dart'; import 'package:spotube/provider/scrobbler/scrobbler.dart'; -import 'package:spotube/provider/server/sourced_track.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/audio_services.dart'; @@ -101,16 +102,18 @@ class AudioPlayerStreamListeners { /// The [Track] from Playlist.getTracks doesn't contain artist images /// so we need to fetch them from the API - final activeTrack = - Track.fromJson(audioPlayerState.activeTrack!.toJson()); - if (audioPlayerState.activeTrack!.artists - ?.any((a) => a.images == null) ?? - false) { - activeTrack.artists = - await ref.read(spotifyProvider).api.artists.list([ - for (final artist in audioPlayerState.activeTrack!.artists!) - artist.id!, - ]).then((value) => value.toList()); + var activeTrack = audioPlayerState.activeTrack!; + if (activeTrack.artists.any((a) => a.images == null)) { + final metadataPlugin = await ref.read(metadataPluginProvider.future); + final artists = await Future.wait( + activeTrack.artists + .map((artist) => metadataPlugin!.artist.getArtist(artist.id)), + ); + activeTrack = activeTrack.copyWith( + artists: artists + .map((e) => SpotubeSimpleArtistObject.fromJson(e.toJson())) + .toList(), + ); } await history.addTrack(activeTrack); @@ -127,24 +130,26 @@ class AudioPlayerStreamListeners { (event.inSeconds / max(audioPlayer.duration.inSeconds, 1)) * 100; try { if (percentProgress < 80 || - audioPlayerState.playlist.index == -1 || - audioPlayerState.playlist.index == + audioPlayerState.currentIndex == -1 || + audioPlayerState.currentIndex == audioPlayerState.tracks.length - 1) { return; } - final nextTrack = SpotubeMedia.fromMedia( - audioPlayerState.playlist.medias - .elementAt(audioPlayerState.playlist.index + 1), - ); + final nextTrack = audioPlayerState.tracks + .elementAt(audioPlayerState.currentIndex + 1); - if (lastTrack == nextTrack.track.id || nextTrack.track is LocalTrack) { + if (lastTrack == nextTrack.id || nextTrack is SpotubeLocalTrackObject) { return; } try { - await ref.read(sourcedTrackProvider(nextTrack).future); + await ref.read( + trackSourcesProvider( + TrackSourceQuery.fromTrack(nextTrack as SpotubeFullTrackObject), + ).future, + ); } finally { - lastTrack = nextTrack.track.id!; + lastTrack = nextTrack.id; } } catch (e, stack) { AppLogger.reportError(e, stack); diff --git a/lib/provider/audio_player/querying_track_info.dart b/lib/provider/audio_player/querying_track_info.dart index 55590d48..d7e271ae 100644 --- a/lib/provider/audio_player/querying_track_info.dart +++ b/lib/provider/audio_player/querying_track_info.dart @@ -1,24 +1,20 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/server/sourced_track.dart'; -import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/provider/server/track_sources.dart'; final queryingTrackInfoProvider = Provider((ref) { - final media = audioPlayer.playlist.index == -1 || - audioPlayer.playlist.medias.isEmpty - ? null - : audioPlayer.playlist.medias.elementAtOrNull(audioPlayer.playlist.index); - final audioPlayerActiveTrack = - media == null ? null : SpotubeMedia.fromMedia(media); + final audioPlayer = ref.watch(audioPlayerProvider); - final activeMedia = ref.watch(audioPlayerProvider.select( - (s) => s.activeMedia == null - ? null - : SpotubeMedia.fromMedia(s.activeMedia!), - )) ?? - audioPlayerActiveTrack; + if (audioPlayer.activeTrack == null) { + return false; + } - if (activeMedia == null) return false; - - return ref.watch(sourcedTrackProvider(activeMedia)).isLoading; + return ref + .watch(trackSourcesProvider( + TrackSourceQuery.fromTrack( + audioPlayer.activeTrack! as SpotubeFullTrackObject), + )) + .isLoading; }); diff --git a/lib/provider/audio_player/state.dart b/lib/provider/audio_player/state.dart index 0e3004f5..bb0527bf 100644 --- a/lib/provider/audio_player/state.dart +++ b/lib/provider/audio_player/state.dart @@ -1,104 +1,60 @@ +import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:media_kit/media_kit.dart' hide Track; -import 'package:spotify/spotify.dart' hide Playlist; -import 'package:spotube/services/audio_player/audio_player.dart'; +import 'package:spotube/models/metadata/metadata.dart'; -class AudioPlayerState { - final bool playing; - final PlaylistMode loopMode; - final bool shuffled; - final Playlist playlist; +part 'state.freezed.dart'; +part 'state.g.dart'; - final List tracks; - final List collections; +@freezed +class AudioPlayerState with _$AudioPlayerState { + const AudioPlayerState._(); - AudioPlayerState({ - required this.playing, - required this.loopMode, - required this.shuffled, - required this.playlist, - required this.collections, - List? tracks, - }) : tracks = tracks ?? - playlist.medias - .map((media) => SpotubeMedia.fromMedia(media).track) - .toList(); + factory AudioPlayerState._inner({ + required bool playing, + required PlaylistMode loopMode, + required bool shuffled, + required List collections, + @Default(0) int currentIndex, + @Default([]) List tracks, + }) = _AudioPlayerState; - factory AudioPlayerState.fromJson(Map json) { - return AudioPlayerState( - playing: json['playing'], - loopMode: PlaylistMode.values.firstWhere( - (e) => e.name == json['loopMode'], - orElse: () => audioPlayer.loopMode, - ), - shuffled: json['shuffled'], - playlist: Playlist( - json['playlist']['medias'] - .map( - (media) => SpotubeMedia.fromMedia(Media( - media['uri'], - extras: media['extras'], - httpHeaders: media['httpHeaders'], - )), - ) - .cast() - .toList(), - index: json['playlist']['index'], - ), - collections: List.from(json['collections']), - ); - } - - Map toJson() { - return { - 'playing': playing, - 'loopMode': loopMode.name, - 'shuffled': shuffled, - 'playlist': { - 'medias': playlist.medias - .map((media) => { - 'uri': media.uri, - 'extras': media.extras, - 'httpHeaders': media.httpHeaders, - }) - .toList(), - 'index': playlist.index, - }, - 'collections': collections, - }; - } - - AudioPlayerState copyWith({ - bool? playing, - PlaylistMode? loopMode, - bool? shuffled, - Playlist? playlist, - List? collections, + factory AudioPlayerState({ + required bool playing, + required PlaylistMode loopMode, + required bool shuffled, + required List collections, + int currentIndex = 0, + List tracks = const [], }) { - return AudioPlayerState( - playing: playing ?? this.playing, - loopMode: loopMode ?? this.loopMode, - shuffled: shuffled ?? this.shuffled, - playlist: playlist ?? this.playlist, - collections: collections ?? this.collections, - tracks: playlist == null ? tracks : null, + assert( + tracks.every((track) => + track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject), + 'All tracks must be either SpotubeFullTrackObject or SpotubeLocalTrackObject', + ); + + return AudioPlayerState._inner( + playing: playing, + loopMode: loopMode, + shuffled: shuffled, + currentIndex: currentIndex, + tracks: tracks, + collections: collections, ); } - Track? get activeTrack { - if (playlist.index == -1) return null; - return tracks.elementAtOrNull(playlist.index); + factory AudioPlayerState.fromJson(Map json) => + _$AudioPlayerStateFromJson(json); + + SpotubeTrackObject? get activeTrack { + if (currentIndex < 0 || currentIndex >= tracks.length) return null; + return tracks[currentIndex]; } - Media? get activeMedia { - if (playlist.index == -1 || playlist.medias.isEmpty) return null; - return playlist.medias.elementAt(playlist.index); - } - - bool containsTrack(Track track) { + bool containsTrack(SpotubeTrackObject track) { return tracks.any((t) => t.id == track.id); } - bool containsTracks(List tracks) { + bool containsTracks(List tracks) { return tracks.every(containsTrack); } diff --git a/lib/provider/audio_player/state.freezed.dart b/lib/provider/audio_player/state.freezed.dart new file mode 100644 index 00000000..0299cd2f --- /dev/null +++ b/lib/provider/audio_player/state.freezed.dart @@ -0,0 +1,297 @@ +// 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 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'); + +AudioPlayerState _$AudioPlayerStateFromJson(Map json) { + return _AudioPlayerState.fromJson(json); +} + +/// @nodoc +mixin _$AudioPlayerState { + bool get playing => throw _privateConstructorUsedError; + PlaylistMode get loopMode => throw _privateConstructorUsedError; + bool get shuffled => throw _privateConstructorUsedError; + List get collections => throw _privateConstructorUsedError; + int get currentIndex => throw _privateConstructorUsedError; + List get tracks => throw _privateConstructorUsedError; + + /// Serializes this AudioPlayerState to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $AudioPlayerStateCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $AudioPlayerStateCopyWith<$Res> { + factory $AudioPlayerStateCopyWith( + AudioPlayerState value, $Res Function(AudioPlayerState) then) = + _$AudioPlayerStateCopyWithImpl<$Res, AudioPlayerState>; + @useResult + $Res call( + {bool playing, + PlaylistMode loopMode, + bool shuffled, + List collections, + int currentIndex, + List tracks}); +} + +/// @nodoc +class _$AudioPlayerStateCopyWithImpl<$Res, $Val extends AudioPlayerState> + implements $AudioPlayerStateCopyWith<$Res> { + _$AudioPlayerStateCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? playing = null, + Object? loopMode = null, + Object? shuffled = null, + Object? collections = null, + Object? currentIndex = null, + Object? tracks = null, + }) { + return _then(_value.copyWith( + playing: null == playing + ? _value.playing + : playing // ignore: cast_nullable_to_non_nullable + as bool, + loopMode: null == loopMode + ? _value.loopMode + : loopMode // ignore: cast_nullable_to_non_nullable + as PlaylistMode, + shuffled: null == shuffled + ? _value.shuffled + : shuffled // ignore: cast_nullable_to_non_nullable + as bool, + collections: null == collections + ? _value.collections + : collections // ignore: cast_nullable_to_non_nullable + as List, + currentIndex: null == currentIndex + ? _value.currentIndex + : currentIndex // ignore: cast_nullable_to_non_nullable + as int, + tracks: null == tracks + ? _value.tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$AudioPlayerStateImplCopyWith<$Res> + implements $AudioPlayerStateCopyWith<$Res> { + factory _$$AudioPlayerStateImplCopyWith(_$AudioPlayerStateImpl value, + $Res Function(_$AudioPlayerStateImpl) then) = + __$$AudioPlayerStateImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {bool playing, + PlaylistMode loopMode, + bool shuffled, + List collections, + int currentIndex, + List tracks}); +} + +/// @nodoc +class __$$AudioPlayerStateImplCopyWithImpl<$Res> + extends _$AudioPlayerStateCopyWithImpl<$Res, _$AudioPlayerStateImpl> + implements _$$AudioPlayerStateImplCopyWith<$Res> { + __$$AudioPlayerStateImplCopyWithImpl(_$AudioPlayerStateImpl _value, + $Res Function(_$AudioPlayerStateImpl) _then) + : super(_value, _then); + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? playing = null, + Object? loopMode = null, + Object? shuffled = null, + Object? collections = null, + Object? currentIndex = null, + Object? tracks = null, + }) { + return _then(_$AudioPlayerStateImpl( + playing: null == playing + ? _value.playing + : playing // ignore: cast_nullable_to_non_nullable + as bool, + loopMode: null == loopMode + ? _value.loopMode + : loopMode // ignore: cast_nullable_to_non_nullable + as PlaylistMode, + shuffled: null == shuffled + ? _value.shuffled + : shuffled // ignore: cast_nullable_to_non_nullable + as bool, + collections: null == collections + ? _value._collections + : collections // ignore: cast_nullable_to_non_nullable + as List, + currentIndex: null == currentIndex + ? _value.currentIndex + : currentIndex // ignore: cast_nullable_to_non_nullable + as int, + tracks: null == tracks + ? _value._tracks + : tracks // ignore: cast_nullable_to_non_nullable + as List, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$AudioPlayerStateImpl extends _AudioPlayerState { + _$AudioPlayerStateImpl( + {required this.playing, + required this.loopMode, + required this.shuffled, + required final List collections, + this.currentIndex = 0, + final List tracks = const []}) + : _collections = collections, + _tracks = tracks, + super._(); + + factory _$AudioPlayerStateImpl.fromJson(Map json) => + _$$AudioPlayerStateImplFromJson(json); + + @override + final bool playing; + @override + final PlaylistMode loopMode; + @override + final bool shuffled; + final List _collections; + @override + List get collections { + if (_collections is EqualUnmodifiableListView) return _collections; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_collections); + } + + @override + @JsonKey() + final int currentIndex; + final List _tracks; + @override + @JsonKey() + List get tracks { + if (_tracks is EqualUnmodifiableListView) return _tracks; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_tracks); + } + + @override + String toString() { + return 'AudioPlayerState._inner(playing: $playing, loopMode: $loopMode, shuffled: $shuffled, collections: $collections, currentIndex: $currentIndex, tracks: $tracks)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$AudioPlayerStateImpl && + (identical(other.playing, playing) || other.playing == playing) && + (identical(other.loopMode, loopMode) || + other.loopMode == loopMode) && + (identical(other.shuffled, shuffled) || + other.shuffled == shuffled) && + const DeepCollectionEquality() + .equals(other._collections, _collections) && + (identical(other.currentIndex, currentIndex) || + other.currentIndex == currentIndex) && + const DeepCollectionEquality().equals(other._tracks, _tracks)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash( + runtimeType, + playing, + loopMode, + shuffled, + const DeepCollectionEquality().hash(_collections), + currentIndex, + const DeepCollectionEquality().hash(_tracks)); + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => + __$$AudioPlayerStateImplCopyWithImpl<_$AudioPlayerStateImpl>( + this, _$identity); + + @override + Map toJson() { + return _$$AudioPlayerStateImplToJson( + this, + ); + } +} + +abstract class _AudioPlayerState extends AudioPlayerState { + factory _AudioPlayerState( + {required final bool playing, + required final PlaylistMode loopMode, + required final bool shuffled, + required final List collections, + final int currentIndex, + final List tracks}) = _$AudioPlayerStateImpl; + _AudioPlayerState._() : super._(); + + factory _AudioPlayerState.fromJson(Map json) = + _$AudioPlayerStateImpl.fromJson; + + @override + bool get playing; + @override + PlaylistMode get loopMode; + @override + bool get shuffled; + @override + List get collections; + @override + int get currentIndex; + @override + List get tracks; + + /// Create a copy of AudioPlayerState + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => + throw _privateConstructorUsedError; +} diff --git a/lib/provider/audio_player/state.g.dart b/lib/provider/audio_player/state.g.dart new file mode 100644 index 00000000..de5f6f1c --- /dev/null +++ b/lib/provider/audio_player/state.g.dart @@ -0,0 +1,40 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'state.dart'; + +// ************************************************************************** +// JsonSerializableGenerator +// ************************************************************************** + +_$AudioPlayerStateImpl _$$AudioPlayerStateImplFromJson(Map json) => + _$AudioPlayerStateImpl( + playing: json['playing'] as bool, + loopMode: $enumDecode(_$PlaylistModeEnumMap, json['loopMode']), + shuffled: json['shuffled'] as bool, + collections: (json['collections'] as List) + .map((e) => e as String) + .toList(), + currentIndex: (json['currentIndex'] as num?)?.toInt() ?? 0, + tracks: (json['tracks'] as List?) + ?.map((e) => SpotubeTrackObject.fromJson( + Map.from(e as Map))) + .toList() ?? + const [], + ); + +Map _$$AudioPlayerStateImplToJson( + _$AudioPlayerStateImpl instance) => + { + 'playing': instance.playing, + 'loopMode': _$PlaylistModeEnumMap[instance.loopMode]!, + 'shuffled': instance.shuffled, + 'collections': instance.collections, + 'currentIndex': instance.currentIndex, + 'tracks': instance.tracks.map((e) => e.toJson()).toList(), + }; + +const _$PlaylistModeEnumMap = { + PlaylistMode.none: 'none', + PlaylistMode.single: 'single', + PlaylistMode.loop: 'loop', +}; diff --git a/lib/provider/blacklist_provider.dart b/lib/provider/blacklist_provider.dart index a51d399f..ff7ec8fb 100644 --- a/lib/provider/blacklist_provider.dart +++ b/lib/provider/blacklist_provider.dart @@ -1,8 +1,8 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotify/spotify.dart'; -import 'package:spotube/models/current_playlist.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; class BlackListNotifier extends AsyncNotifier> { @@ -34,17 +34,15 @@ class BlackListNotifier extends AsyncNotifier> { .go(); } - bool contains(TrackSimple track) { + bool contains(SpotubeTrackObject track) { final containsTrack = state.asData?.value.any((element) => element.elementId == track.id) ?? false; - final containsTrackArtists = track.artists?.any( - (artist) => - state.asData?.value.any((el) => el.elementId == artist.id) ?? - false, - ) ?? - false; + final containsTrackArtists = track.artists.any( + (artist) => + state.asData?.value.any((el) => el.elementId == artist.id) ?? false, + ); return containsTrack || containsTrackArtists; } @@ -56,18 +54,9 @@ class BlackListNotifier extends AsyncNotifier> { } /// Filters the non blacklisted tracks from the given [tracks] - Iterable filter(Iterable tracks) { + Iterable filter(Iterable tracks) { return tracks.whereNot(contains).toList(); } - - CurrentPlaylist filterPlaylist(CurrentPlaylist playlist) { - return CurrentPlaylist( - id: playlist.id, - name: playlist.name, - thumbnail: playlist.thumbnail, - tracks: playlist.tracks.where((track) => !contains(track)).toList(), - ); - } } final blacklistProvider = diff --git a/lib/provider/connect/connect.dart b/lib/provider/connect/connect.dart index 93d2fb88..268b6567 100644 --- a/lib/provider/connect/connect.dart +++ b/lib/provider/connect/connect.dart @@ -5,11 +5,11 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/provider/connect/clients.dart'; @@ -41,7 +41,8 @@ final queueProvider = StateProvider( playing: audioPlayer.isPlaying, loopMode: audioPlayer.loopMode, shuffled: audioPlayer.isShuffled, - playlist: audioPlayer.playlist, + tracks: [], + currentIndex: 0, collections: [], ), ); @@ -207,7 +208,7 @@ class ConnectNotifier extends AsyncNotifier { emit(WebSocketLoopEvent(value)); } - Future addTrack(Track data) async { + Future addTrack(SpotubeFullTrackObject data) async { emit(WebSocketAddTrackEvent(data)); } diff --git a/lib/provider/discord_provider.dart b/lib/provider/discord_provider.dart index 8f81fc51..ee068f10 100644 --- a/lib/provider/discord_provider.dart +++ b/lib/provider/discord_provider.dart @@ -2,8 +2,7 @@ import 'dart:async'; import 'package:flutter_discord_rpc/flutter_discord_rpc.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/artist_simple.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -74,20 +73,20 @@ class DiscordNotifier extends AsyncNotifier { } } - Future updatePresence(Track track) async { + Future updatePresence(SpotubeTrackObject track) async { if (!kIsDesktop) return; if (FlutterDiscordRPC.instance.isConnected == false) return; - final artistNames = track.artists?.asString(); + final artistNames = track.artists.asString(); final isPlaying = audioPlayer.isPlaying; final position = audioPlayer.position; await FlutterDiscordRPC.instance.setActivity( activity: RPCActivity( details: track.name, - state: artistNames != null ? "by $artistNames" : null, + state: artistNames, assets: RPCAssets( largeImage: - track.album?.images?.first.url ?? "spotube-logo-foreground", + track.album?.images.first.url ?? "spotube-logo-foreground", largeText: track.album?.name ?? "Unknown album", smallImage: "spotube-logo-foreground", smallText: "Spotube", @@ -95,8 +94,7 @@ class DiscordNotifier extends AsyncNotifier { buttons: [ RPCButton( label: "Listen on Spotify", - url: track.externalUrls?.spotify ?? - "https://open.spotify.com/tracks/${track.id}", + url: track.externalUri, ), ], timestamps: RPCTimestamps( diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index 1b588399..a7e2b768 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -1,16 +1,15 @@ import 'dart:async'; import 'dart:io'; -import 'package:spotube/extensions/track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/server/track_sources.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:path/path.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/artist_simple.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/download_manager/download_manager.dart'; import 'package:spotube/services/sourced_track/enums.dart'; @@ -21,18 +20,22 @@ import 'package:spotube/utils/service_utils.dart'; class DownloadManagerProvider extends ChangeNotifier { DownloadManagerProvider({required this.ref}) : $history = {}, - $backHistory = {}, + $backHistory = {}, dl = DownloadManager() { dl.statusStream.listen((event) async { try { final (:request, :status) = event; - final track = $history.firstWhereOrNull( + final sourcedTrack = $history.firstWhereOrNull( (element) => element.getUrlOfCodec(downloadCodec) == request.url, ); + if (sourcedTrack == null) return; + final track = $backHistory.firstWhereOrNull( + (element) => element.id == sourcedTrack.query.id, + ); if (track == null) return; - final savePath = getTrackFileUrl(track); + final savePath = getTrackFileUrl(sourcedTrack); // related to onFileExists final oldFile = File("$savePath.old"); @@ -57,7 +60,7 @@ class DownloadManagerProvider extends ChangeNotifier { } final imageBytes = await ServiceUtils.downloadImage( - (track.album?.images).asUrlString( + (track.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, index: 1, ), @@ -78,7 +81,8 @@ class DownloadManagerProvider extends ChangeNotifier { }); } - Future Function(Track track) onFileExists = (Track track) async => true; + Future Function(SpotubeFullTrackObject track) onFileExists = + (SpotubeFullTrackObject track) async => true; final Ref ref; @@ -99,21 +103,19 @@ class DownloadManagerProvider extends ChangeNotifier { final Set $history; // these are the tracks which metadata hasn't been fetched yet - final Set $backHistory; + final Set $backHistory; final DownloadManager dl; - String getTrackFileUrl(Track track) { + String getTrackFileUrl(SourcedTrack track) { final name = - "${track.name} - ${track.artists?.asString() ?? ""}.${downloadCodec.name}"; + "${track.query.title} - ${track.query.artists.join(", ")}.${downloadCodec.name}"; return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name)); } - bool isActive(Track track) { + Future isActive(SpotubeFullTrackObject track) async { if ($backHistory.contains(track)) return true; - final sourcedTrack = mapToSourcedTrack(track); - - if (sourcedTrack == null) return false; + final sourcedTrack = await mapToSourcedTrack(track); return dl .getAllDownloads() @@ -128,8 +130,12 @@ class DownloadManagerProvider extends ChangeNotifier { } /// For singular downloads - Future addToQueue(Track track) async { - final savePath = getTrackFileUrl(track); + Future addToQueue(SpotubeFullTrackObject track) async { + final sourcedTrack = await ref.read( + trackSourcesProvider(TrackSourceQuery.fromTrack(track)).future, + ); + + final savePath = getTrackFileUrl(sourcedTrack); final oldFile = File(savePath); if (await oldFile.exists() && !await onFileExists(track)) { @@ -140,18 +146,21 @@ class DownloadManagerProvider extends ChangeNotifier { await oldFile.rename("$savePath.old"); } - if (track is SourcedTrack && track.codec == downloadCodec) { - final downloadTask = - await dl.addDownload(track.getUrlOfCodec(downloadCodec), savePath); + if (sourcedTrack.codec == downloadCodec) { + final downloadTask = await dl.addDownload( + sourcedTrack.getUrlOfCodec(downloadCodec), savePath); if (downloadTask != null) { - $history.add(track); + $history.add(sourcedTrack); } } else { $backHistory.add(track); - final sourcedTrack = await SourcedTrack.fetchFromTrack( - ref: ref, - track: track, - ).then((d) { + final sourcedTrack = await ref + .read( + trackSourcesProvider( + TrackSourceQuery.fromTrack(track), + ).future, + ) + .then((d) { $backHistory.remove(track); return d; }); @@ -167,10 +176,8 @@ class DownloadManagerProvider extends ChangeNotifier { notifyListeners(); } - Future batchAddToQueue(List tracks) async { - $backHistory.addAll( - tracks.where((element) => element is! SourcedTrack), - ); + Future batchAddToQueue(List tracks) async { + $backHistory.addAll(tracks); notifyListeners(); for (final track in tracks) { try { @@ -194,20 +201,23 @@ class DownloadManagerProvider extends ChangeNotifier { $history.remove(track); } - Future pause(SourcedTrack track) { - return dl.pauseDownload(track.getUrlOfCodec(downloadCodec)); + Future pause(SpotubeFullTrackObject track) async { + final sourcedTrack = await mapToSourcedTrack(track); + return dl.pauseDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); } - Future resume(SourcedTrack track) { - return dl.resumeDownload(track.getUrlOfCodec(downloadCodec)); + Future resume(SpotubeFullTrackObject track) async { + final sourcedTrack = await mapToSourcedTrack(track); + return dl.resumeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); } - Future retry(SourcedTrack track) { + Future retry(SpotubeFullTrackObject track) { return addToQueue(track); } - void cancel(SourcedTrack track) { - dl.cancelDownload(track.getUrlOfCodec(downloadCodec)); + void cancel(SpotubeFullTrackObject track) async { + final sourcedTrack = await mapToSourcedTrack(track); + return dl.cancelDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); } void cancelAll() { @@ -217,12 +227,19 @@ class DownloadManagerProvider extends ChangeNotifier { } } - SourcedTrack? mapToSourcedTrack(Track track) { - if (track is SourcedTrack) { - return track; - } else { - return $history.firstWhereOrNull((element) => element.id == track.id); + Future mapToSourcedTrack(SpotubeFullTrackObject track) async { + final historicTrack = + $history.firstWhereOrNull((element) => element.query.id == track.id); + + if (historicTrack != null) { + return historicTrack; } + + final sourcedTrack = await ref.read( + trackSourcesProvider(TrackSourceQuery.fromTrack(track)).future, + ); + + return sourcedTrack; } ValueNotifier? getStatusNotifier(SourcedTrack track) { diff --git a/lib/provider/glance/glance.dart b/lib/provider/glance/glance.dart index 22faa13f..8afeda11 100644 --- a/lib/provider/glance/glance.dart +++ b/lib/provider/glance/glance.dart @@ -5,7 +5,7 @@ import 'package:home_widget/home_widget.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:http/http.dart'; import 'package:logger/logger.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/server/server.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; @@ -71,7 +71,7 @@ Future _updateWidget() async { } } -Future _sendActiveTrack(Track? track) async { +Future _sendActiveTrack(SpotubeTrackObject? track) async { if (track == null) { await _saveWidgetData("activeTrack", null); await _updateWidget(); @@ -80,8 +80,8 @@ Future _sendActiveTrack(Track? track) async { final jsonTrack = track.toJson(); - final image = track.album?.images?.first; - final cachedImage = await DefaultCacheManager().getSingleFile(image!.url!); + final image = track.album?.images.first; + final cachedImage = await DefaultCacheManager().getSingleFile(image!.url); final data = { ...jsonTrack, "album": { diff --git a/lib/provider/history/history.dart b/lib/provider/history/history.dart index 25b722ff..b83e5db1 100644 --- a/lib/provider/history/history.dart +++ b/lib/provider/history/history.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; class PlaybackHistoryActions { @@ -16,31 +16,31 @@ class PlaybackHistoryActions { }); } - Future addPlaylists(List playlists) async { + Future addPlaylists(List playlists) async { await _batchInsertHistoryEntries([ for (final playlist in playlists) HistoryTableCompanion.insert( type: HistoryEntryType.playlist, - itemId: playlist.id!, + itemId: playlist.id, data: playlist.toJson(), ), ]); } - Future addAlbums(List albums) async { + Future addAlbums(List albums) async { await _batchInsertHistoryEntries([ for (final albums in albums) HistoryTableCompanion.insert( type: HistoryEntryType.album, - itemId: albums.id!, + itemId: albums.id, data: albums.toJson(), ), ]); } - Future addTracks(List tracks) async { + Future addTracks(List tracks) async { assert( - tracks.every((t) => t.artists?.every((a) => a.images != null) ?? false), + tracks.every((t) => t.artists.every((a) => a.images != null)), 'Track artists must have images', ); @@ -48,22 +48,22 @@ class PlaybackHistoryActions { for (final track in tracks) HistoryTableCompanion.insert( type: HistoryEntryType.track, - itemId: track.id!, + itemId: track.id, data: track.toJson(), ), ]); } - Future addTrack(Track track) async { + Future addTrack(SpotubeTrackObject track) async { assert( - track.artists?.every((a) => a.images != null) ?? false, + track.artists.every((a) => a.images != null), 'Track artists must have images', ); await _db.into(_db.historyTable).insert( HistoryTableCompanion.insert( type: HistoryEntryType.track, - itemId: track.id!, + itemId: track.id, data: track.toJson(), ), ); diff --git a/lib/provider/scrobbler/scrobbler.dart b/lib/provider/scrobbler/scrobbler.dart index 8aff0438..f65dc759 100644 --- a/lib/provider/scrobbler/scrobbler.dart +++ b/lib/provider/scrobbler/scrobbler.dart @@ -3,16 +3,15 @@ import 'dart:async'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:scrobblenaut/scrobblenaut.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/env.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/logger/logger.dart'; class ScrobblerNotifier extends AsyncNotifier { - final StreamController _scrobbleController = - StreamController.broadcast(); + final StreamController _scrobbleController = + StreamController.broadcast(); @override build() async { final database = ref.watch(databaseProvider); @@ -47,13 +46,12 @@ class ScrobblerNotifier extends AsyncNotifier { _scrobbleController.stream.listen((track) async { try { await state.asData?.value?.track.scrobble( - artist: track.artists!.first.name!, - track: track.name!, - album: track.album!.name!, + artist: track.artists.first.name, + track: track.name, + album: track.album!.name, chosenByUser: true, - duration: track.duration, + duration: Duration(milliseconds: track.durationMs), timestamp: DateTime.now().toUtc(), - trackNumber: track.trackNumber, ); } catch (e, stackTrace) { AppLogger.reportError(e, stackTrace); @@ -109,21 +107,21 @@ class ScrobblerNotifier extends AsyncNotifier { await database.delete(database.scrobblerTable).go(); } - void scrobble(Track track) { + void scrobble(SpotubeTrackObject track) { _scrobbleController.add(track); } - Future love(Track track) async { + Future love(SpotubeTrackObject track) async { await state.asData?.value?.track.love( - artist: track.artists!.asString(), - track: track.name!, + artist: track.artists.asString(), + track: track.name, ); } - Future unlove(Track track) async { + Future unlove(SpotubeTrackObject track) async { await state.asData?.value?.track.unLove( - artist: track.artists!.asString(), - track: track.name!, + artist: track.artists.asString(), + track: track.name, ); } } diff --git a/lib/provider/server/active_sourced_track.dart b/lib/provider/server/active_sourced_track.dart deleted file mode 100644 index 37d0dec8..00000000 --- a/lib/provider/server/active_sourced_track.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; - -class ActiveSourcedTrackNotifier extends Notifier { - @override - build() { - return null; - } - - void update(SourcedTrack? sourcedTrack) { - state = sourcedTrack; - } - - Future populateSibling() async { - if (state == null) return; - state = await state!.copyWithSibling(); - } - - Future swapSibling(SourceInfo sibling) async { - if (state == null) return; - await populateSibling(); - final newTrack = await state!.swapWithSibling(sibling); - if (newTrack == null) return; - - state = newTrack; - await audioPlayer.pause(); - - final playbackNotifier = ref.read(audioPlayerProvider.notifier); - final oldActiveIndex = audioPlayer.currentIndex; - - await playbackNotifier.addTracksAtFirst([newTrack], allowDuplicates: true); - await Future.delayed(const Duration(milliseconds: 50)); - await playbackNotifier.jumpToTrack(newTrack); - - await audioPlayer.removeTrack(oldActiveIndex); - - await audioPlayer.resume(); - } -} - -final activeSourcedTrackProvider = - NotifierProvider( - () => ActiveSourcedTrackNotifier(), -); diff --git a/lib/provider/server/active_track_sources.dart b/lib/provider/server/active_track_sources.dart new file mode 100644 index 00000000..5b64dc26 --- /dev/null +++ b/lib/provider/server/active_track_sources.dart @@ -0,0 +1,42 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/services/sourced_track/sourced_track.dart'; + +final activeTrackSourcesProvider = FutureProvider< + ({ + SourcedTrack? source, + TrackSourcesNotifier? notifier, + SpotubeTrackObject track, + })?>((ref) async { + final audioPlayerState = ref.watch(audioPlayerProvider); + + if (audioPlayerState.activeTrack == null) { + return null; + } + + if (audioPlayerState.activeTrack is SpotubeLocalTrackObject) { + return ( + source: null, + notifier: null, + track: audioPlayerState.activeTrack!, + ); + } + + final trackQuery = TrackSourceQuery.fromTrack( + audioPlayerState.activeTrack! as SpotubeFullTrackObject, + ); + + final sourcedTrack = await ref.watch(trackSourcesProvider(trackQuery).future); + final sourcedTrackNotifier = ref.watch( + trackSourcesProvider(trackQuery).notifier, + ); + + return ( + source: sourcedTrack, + track: audioPlayerState.activeTrack!, + notifier: sourcedTrackNotifier, + ); +}); diff --git a/lib/provider/server/routes/connect.dart b/lib/provider/server/routes/connect.dart index 6c4d8ce0..ccfbf5ba 100644 --- a/lib/provider/server/routes/connect.dart +++ b/lib/provider/server/routes/connect.dart @@ -6,10 +6,10 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shelf/shelf.dart'; import 'package:shelf_web_socket/shelf_web_socket.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -161,19 +161,19 @@ class ServerConnectRoutes { event.onLoad((event) async { await audioPlayerNotifier.load( - event.data.tracks, + event.data.tracks as List, autoPlay: true, initialIndex: event.data.initialIndex ?? 0, ); if (event.data.collectionId == null) return; audioPlayerNotifier.addCollection(event.data.collectionId!); - if (event.data.collection is AlbumSimple) { - historyNotifier - .addAlbums([event.data.collection as AlbumSimple]); + if (event.data.collection is SpotubeSimpleAlbumObject) { + historyNotifier.addAlbums( + [event.data.collection as SpotubeSimpleAlbumObject]); } else { historyNotifier.addPlaylists( - [event.data.collection as PlaylistSimple]); + [event.data.collection as SpotubeSimplePlaylistObject]); } }); diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index ccbeb7ab..eeedc47b 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -1,6 +1,7 @@ import 'dart:io'; import 'dart:math'; +import 'package:collection/collection.dart'; import 'package:dio/dio.dart' hide Response; import 'package:dio/dio.dart' as dio_lib; import 'package:flutter/foundation.dart'; @@ -8,15 +9,14 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:path/path.dart'; import 'package:shelf/shelf.dart'; -import 'package:spotube/extensions/artist_simple.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/extensions/track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/parser/range_headers.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; -import 'package:spotube/provider/server/active_sourced_track.dart'; -import 'package:spotube/provider/server/sourced_track.dart'; +import 'package:spotube/provider/server/active_track_sources.dart'; +import 'package:spotube/provider/server/track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -55,7 +55,7 @@ class ServerPlaybackRoutes { join( await UserPreferencesNotifier.getMusicCacheDir(), ServiceUtils.sanitizeFilename( - '${track.name} - ${track.artists?.asString()} (${track.sourceInfo.id}).${track.codec.name}', + '${track.query.title} - ${track.query.artists.join(",")} (${track.info.id}).${track.codec.name}', ), ), ); @@ -127,16 +127,16 @@ class ServerPlaybackRoutes { .catchError((e, stack) async { AppLogger.reportError(e, stack); final sourcedTrack = await ref - .read(sourcedTrackProvider(SpotubeMedia(track)).notifier) + .read(trackSourcesProvider(track.query).notifier) .refreshStreamingUrl(); - if (playlist.activeTrack?.id == sourcedTrack?.id && - sourcedTrack != null) { - ref.read(activeSourcedTrackProvider.notifier).update(sourcedTrack); - } + // It gets updated by itself. + // if (playlist.activeTrack?.id == sourcedTrack.query.id) { + // ref.read(activeTrackSourcesProvider.notifier).update(sourcedTrack); + // } return await dio.get( - sourcedTrack!.url, + sourcedTrack.url, options: options.copyWith(headers: { ...?options.headers, "user-agent": _randomUserAgent, @@ -174,8 +174,18 @@ class ServerPlaybackRoutes { } if (contentRange.total == fileLength && track.codec != SourceCodecs.weba) { + final playlistTrack = playlist.tracks.firstWhereOrNull( + (element) => element.id == track.query.id, + ); + if (playlistTrack == null) { + AppLogger.log.e( + "Track ${track.query.id} not found in playlist, cannot write metadata.", + ); + return (response: res, bytes: bytes); + } + final imageBytes = await ServiceUtils.downloadImage( - (track.album?.images).asUrlString( + (playlistTrack.album?.images).asUrlString( placeholder: ImagePlaceholder.albumArt, index: 1, ), @@ -183,9 +193,9 @@ class ServerPlaybackRoutes { await MetadataGod.writeMetadata( file: trackCacheFile.path, - metadata: track.toMetadata( - fileLength: fileLength, + metadata: (playlistTrack as SpotubeFullTrackObject).toMetadata( imageBytes: imageBytes, + fileLength: fileLength, ), ); } @@ -199,15 +209,21 @@ class ServerPlaybackRoutes { final track = playlist.tracks.firstWhere((element) => element.id == trackId); - final activeSourcedTrack = ref.read(activeSourcedTrackProvider); - final sourcedTrack = activeSourcedTrack?.id == track.id - ? activeSourcedTrack - : await ref.read(sourcedTrackProvider(SpotubeMedia(track)).future); + final activeSourcedTrack = + await ref.read(activeTrackSourcesProvider.future); + final sourcedTrack = activeSourcedTrack?.track.id == track.id + ? activeSourcedTrack?.source + : await ref.read( + trackSourcesProvider( + TrackSourceQuery.parseUri(request.url.toString()), + ).future, + ); - if (playlist.activeTrack?.id == sourcedTrack?.id && - sourcedTrack != null) { - ref.read(activeSourcedTrackProvider.notifier).update(sourcedTrack); - } + // This will be automatically updated by the notifier. + // if (playlist.activeTrack?.id == sourcedTrack?.query.id && + // sourcedTrack != null) { + // ref.read(activeTrackSourcesProvider.notifier).update(sourcedTrack); + // } final (bytes: audioBytes, response: res) = await streamTrack(sourcedTrack!, request.headers); diff --git a/lib/provider/server/sourced_track.dart b/lib/provider/server/sourced_track.dart deleted file mode 100644 index f733f9d6..00000000 --- a/lib/provider/server/sourced_track.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/models/local_track.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; - -class SourcedTrackNotifier - extends FamilyAsyncNotifier { - @override - build(media) async { - final track = media?.track; - if (track == null || track is LocalTrack) { - return null; - } - - ref.listen( - audioPlayerProvider.select((value) => value.tracks), - (old, next) { - if (next.isEmpty || next.none((element) => element.id == track.id)) { - ref.invalidateSelf(); - } - }, - ); - - final sourcedTrack = - await SourcedTrack.fetchFromTrack(track: track, ref: ref); - - return sourcedTrack; - } - - Future refreshStreamingUrl() async { - if (arg == null) { - return null; - } - - return await update((prev) async { - return await SourcedTrack.fetchFromTrack( - track: state.value!, - ref: ref, - ); - }); - } -} - -final sourcedTrackProvider = AsyncNotifierProviderFamily( - () => SourcedTrackNotifier(), -); diff --git a/lib/provider/server/track_sources.dart b/lib/provider/server/track_sources.dart new file mode 100644 index 00000000..2112c2af --- /dev/null +++ b/lib/provider/server/track_sources.dart @@ -0,0 +1,45 @@ +import 'dart:async'; + +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; +import 'package:spotube/services/sourced_track/sourced_track.dart'; + +class TrackSourcesNotifier + extends FamilyAsyncNotifier { + @override + FutureOr build(query) { + ref.watch(userPreferencesProvider.select((p) => p.audioQuality)); + ref.watch(userPreferencesProvider.select((p) => p.audioSource)); + ref.watch(userPreferencesProvider.select((p) => p.streamMusicCodec)); + ref.watch(userPreferencesProvider.select((p) => p.downloadMusicCodec)); + + return SourcedTrack.fetchFromQuery(query: query, ref: ref); + } + + Future refreshStreamingUrl() async { + return await update((prev) async { + return await prev.refreshStream(); + }); + } + + Future copyWithSibling( + TrackSourceInfo info, + TrackSourceQuery query, + ) async { + return await update((prev) async { + return prev.copyWithSibling(); + }); + } + + Future swapWithSibling(TrackSourceInfo sibling) async { + return await update((prev) async { + return await prev.swapWithSibling(sibling) ?? prev; + }); + } +} + +final trackSourcesProvider = AsyncNotifierProviderFamily( + () => TrackSourcesNotifier(), +); diff --git a/lib/provider/skip_segments/skip_segments.dart b/lib/provider/skip_segments/skip_segments.dart index 005797f4..accccddd 100644 --- a/lib/provider/skip_segments/skip_segments.dart +++ b/lib/provider/skip_segments/skip_segments.dart @@ -1,9 +1,10 @@ import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:dio/dio.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/server/active_sourced_track.dart'; +import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/dio/dio.dart'; @@ -81,8 +82,11 @@ Future> getAndCacheSkipSegments( final segmentProvider = FutureProvider( (ref) async { - final track = ref.watch(activeSourcedTrackProvider); - if (track == null) return null; + final snapshot = await ref.watch(activeTrackSourcesProvider.future); + if (snapshot == null) return null; + final (:track, :source, :notifier) = snapshot; + if (track is SpotubeLocalTrackObject) return null; + if (source!.source case AudioSource.jiosaavn) return null; final skipNonMusic = ref.watch( userPreferencesProvider.select( @@ -96,16 +100,13 @@ final segmentProvider = FutureProvider( ); if (!skipNonMusic) { - return SourcedSegments( - segments: [], - source: track.sourceInfo.id, - ); + return SourcedSegments(segments: [], source: source.info.id); } - final segments = await getAndCacheSkipSegments(track.sourceInfo.id, ref); + final segments = await getAndCacheSkipSegments(source.info.id, ref); return SourcedSegments( - source: track.sourceInfo.id, + source: source.info.id, segments: segments, ); }, diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index 4febecdf..ead81967 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -1,9 +1,10 @@ import 'dart:io'; import 'package:media_kit/media_kit.dart' hide Track; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; -import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/local_track.dart'; import 'package:spotube/services/audio_player/custom_player.dart'; import 'dart:async'; @@ -11,33 +12,41 @@ import 'dart:async'; import 'package:media_kit/media_kit.dart' as mk; import 'package:spotube/services/audio_player/playback_state.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/platform.dart'; part 'audio_players_streams_mixin.dart'; part 'audio_player_impl.dart'; class SpotubeMedia extends mk.Media { - final Track track; - static int serverPort = 0; + final SpotubeTrackObject track; + + static String get _host => + kIsWindows ? "localhost" : InternetAddress.anyIPv4.address; + + static String _queries(SpotubeFullTrackObject track) { + final params = TrackSourceQuery.fromTrack(track).toJson(); + + return params.entries + .map((e) => + "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}") + .join("&"); + } + SpotubeMedia( this.track, { Map? extras, super.httpHeaders, - }) : super( - track is LocalTrack + }) : assert( + track is SpotubeLocalTrackObject || track is SpotubeFullTrackObject, + "Track must be a either a local track or a full track object with ISRC", + ), + // If the track is a local track, use its path, otherwise use the server URL + super( + track is SpotubeLocalTrackObject ? track.path - : "http://${kIsWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}", - extras: { - ...?extras, - "track": switch (track) { - LocalTrack() => track.toJson(), - SourcedTrack() => track.toJson(), - _ => track.toJson(), - }, - }, + : "http://$_host:$serverPort/stream/${track.id}?${_queries(track as SpotubeFullTrackObject)}", ); @override @@ -46,23 +55,11 @@ class SpotubeMedia extends mk.Media { /// [super.uri] must be used instead of [track.path] to prevent wrong /// path format exceptions in Windows causing [extras] to be null LocalTrack() => super.uri, - _ => - "http://${kIsWindows ? "localhost" : InternetAddress.anyIPv4.address}:" - "$serverPort/stream/${track.id}", + _ => "http://$_host:" + "$serverPort/stream/${track.id}", }; } - factory SpotubeMedia.fromMedia(mk.Media media) { - final track = media.uri.startsWith("http") - ? Track.fromJson(media.extras?["track"]) - : LocalTrack.fromJson(media.extras?["track"]); - return SpotubeMedia( - track, - extras: media.extras, - httpHeaders: media.httpHeaders, - ); - } - // @override // operator ==(Object other) { // if (other is! SpotubeMedia) return false; diff --git a/lib/services/audio_services/audio_services.dart b/lib/services/audio_services/audio_services.dart index 060a7f41..b3653555 100644 --- a/lib/services/audio_services/audio_services.dart +++ b/lib/services/audio_services/audio_services.dart @@ -1,10 +1,8 @@ import 'package:audio_service/audio_service.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/env.dart'; -import 'package:spotube/extensions/artist_simple.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/mobile_audio_service.dart'; @@ -49,16 +47,14 @@ class AudioServices with WidgetsBindingObserver { return AudioServices(mobile, smtc); } - Future addTrack(Track track) async { + Future addTrack(SpotubeTrackObject track) async { await smtc?.addTrack(track); mobile?.addItem(MediaItem( - id: track.id!, + id: track.id, album: track.album?.name ?? "", - title: track.name!, - artist: (track.artists)?.asString() ?? "", - duration: track is SourcedTrack - ? track.sourceInfo.duration - : Duration(milliseconds: track.durationMs ?? 0), + title: track.name, + artist: track.artists.asString(), + duration: Duration(milliseconds: track.durationMs), artUri: Uri.parse( (track.album?.images).asUrlString( placeholder: ImagePlaceholder.albumArt, diff --git a/lib/services/audio_services/windows_audio_service.dart b/lib/services/audio_services/windows_audio_service.dart index 8edc5069..6cf101ab 100644 --- a/lib/services/audio_services/windows_audio_service.dart +++ b/lib/services/audio_services/windows_audio_service.dart @@ -2,9 +2,7 @@ import 'dart:async'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:smtc_windows/smtc_windows.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/artist_simple.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/playback_state.dart'; @@ -77,15 +75,15 @@ class WindowsAudioService { ]); } - Future addTrack(Track track) async { + Future addTrack(SpotubeTrackObject track) async { if (!smtc.enabled) { await smtc.enableSmtc(); } await smtc.updateMetadata( MusicMetadata( - title: track.name!, - albumArtist: track.artists?.firstOrNull?.name ?? "Unknown", - artist: track.artists?.asString() ?? "Unknown", + title: track.name, + albumArtist: track.artists.firstOrNull?.name ?? "Unknown", + artist: track.artists.asString(), album: track.album?.name ?? "Unknown", thumbnail: (track.album?.images).asUrlString( placeholder: ImagePlaceholder.albumArt, diff --git a/lib/services/sourced_track/enums.dart b/lib/services/sourced_track/enums.dart index e47ee6bd..9ef6c080 100644 --- a/lib/services/sourced_track/enums.dart +++ b/lib/services/sourced_track/enums.dart @@ -1,5 +1,4 @@ -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; +import 'package:spotube/models/playback/track_sources.dart'; enum SourceCodecs { m4a._("M4a (Best for downloaded music)"), @@ -15,4 +14,7 @@ enum SourceQualities { low, } -typedef SiblingType = ({T info, SourceMap? source}); +typedef SiblingType = ({ + T info, + List? source +}); diff --git a/lib/services/sourced_track/exceptions.dart b/lib/services/sourced_track/exceptions.dart index 85bc5b27..c841e1e2 100644 --- a/lib/services/sourced_track/exceptions.dart +++ b/lib/services/sourced_track/exceptions.dart @@ -1,12 +1,12 @@ -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/playback/track_sources.dart'; class TrackNotFoundError extends Error { - final Track track; + final TrackSourceQuery track; TrackNotFoundError(this.track); @override String toString() { - return '[TrackNotFoundError] ${track.name} - ${track.artists?.map((e) => e.name).join(", ")}'; + return '[TrackNotFoundError] ${track.title} - ${track.artists.join(", ")}'; } } diff --git a/lib/services/sourced_track/models/source_info.dart b/lib/services/sourced_track/models/source_info.dart deleted file mode 100644 index 4ba90355..00000000 --- a/lib/services/sourced_track/models/source_info.dart +++ /dev/null @@ -1,33 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'source_info.g.dart'; - -@JsonSerializable() -class SourceInfo { - final String id; - final String title; - final String artist; - final String artistUrl; - final String? album; - - final String thumbnail; - final String pageUrl; - - final Duration duration; - - SourceInfo({ - required this.id, - required this.title, - required this.artist, - required this.thumbnail, - required this.pageUrl, - required this.duration, - required this.artistUrl, - this.album, - }); - - factory SourceInfo.fromJson(Map json) => - _$SourceInfoFromJson(json); - - Map toJson() => _$SourceInfoToJson(this); -} diff --git a/lib/services/sourced_track/models/source_info.g.dart b/lib/services/sourced_track/models/source_info.g.dart deleted file mode 100644 index 54671f63..00000000 --- a/lib/services/sourced_track/models/source_info.g.dart +++ /dev/null @@ -1,30 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'source_info.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SourceInfo _$SourceInfoFromJson(Map json) => SourceInfo( - id: json['id'] as String, - title: json['title'] as String, - artist: json['artist'] as String, - thumbnail: json['thumbnail'] as String, - pageUrl: json['pageUrl'] as String, - duration: Duration(microseconds: (json['duration'] as num).toInt()), - artistUrl: json['artistUrl'] as String, - album: json['album'] as String?, - ); - -Map _$SourceInfoToJson(SourceInfo instance) => - { - 'id': instance.id, - 'title': instance.title, - 'artist': instance.artist, - 'artistUrl': instance.artistUrl, - 'album': instance.album, - 'thumbnail': instance.thumbnail, - 'pageUrl': instance.pageUrl, - 'duration': instance.duration.inMicroseconds, - }; diff --git a/lib/services/sourced_track/models/source_map.dart b/lib/services/sourced_track/models/source_map.dart deleted file mode 100644 index f99f95e4..00000000 --- a/lib/services/sourced_track/models/source_map.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; -import 'package:spotube/services/sourced_track/enums.dart'; - -part 'source_map.g.dart'; - -@JsonSerializable() -class SourceQualityMap { - final String high; - final String medium; - final String low; - - const SourceQualityMap({ - required this.high, - required this.medium, - required this.low, - }); - - factory SourceQualityMap.fromJson(Map json) => - _$SourceQualityMapFromJson(json); - - Map toJson() => _$SourceQualityMapToJson(this); - - operator [](SourceQualities key) { - switch (key) { - case SourceQualities.high: - return high; - case SourceQualities.medium: - return medium; - case SourceQualities.low: - return low; - } - } -} - -@JsonSerializable() -class SourceMap { - final SourceQualityMap? weba; - final SourceQualityMap? m4a; - - const SourceMap({ - this.weba, - this.m4a, - }); - - factory SourceMap.fromJson(Map json) => - _$SourceMapFromJson(json); - - Map toJson() => _$SourceMapToJson(this); - - operator [](SourceCodecs key) { - switch (key) { - case SourceCodecs.weba: - return weba; - case SourceCodecs.m4a: - return m4a; - } - } -} diff --git a/lib/services/sourced_track/models/source_map.g.dart b/lib/services/sourced_track/models/source_map.g.dart deleted file mode 100644 index a581cc67..00000000 --- a/lib/services/sourced_track/models/source_map.g.dart +++ /dev/null @@ -1,36 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'source_map.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SourceQualityMap _$SourceQualityMapFromJson(Map json) => SourceQualityMap( - high: json['high'] as String, - medium: json['medium'] as String, - low: json['low'] as String, - ); - -Map _$SourceQualityMapToJson(SourceQualityMap instance) => - { - 'high': instance.high, - 'medium': instance.medium, - 'low': instance.low, - }; - -SourceMap _$SourceMapFromJson(Map json) => SourceMap( - weba: json['weba'] == null - ? null - : SourceQualityMap.fromJson( - Map.from(json['weba'] as Map)), - m4a: json['m4a'] == null - ? null - : SourceQualityMap.fromJson( - Map.from(json['m4a'] as Map)), - ); - -Map _$SourceMapToJson(SourceMap instance) => { - 'weba': instance.weba?.toJson(), - 'm4a': instance.m4a?.toJson(), - }; diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index bf0b22e6..f8caa38d 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -1,47 +1,27 @@ +import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/sourced_track/enums.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; import 'package:spotube/services/sourced_track/sources/invidious.dart'; import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/utils/service_utils.dart'; -abstract class SourcedTrack extends Track { - final SourceMap source; - final List siblings; - final SourceInfo sourceInfo; +abstract class SourcedTrack extends BasicSourcedTrack { final Ref ref; SourcedTrack({ required this.ref, - required this.source, - required this.siblings, - required this.sourceInfo, - required Track track, - }) { - id = track.id; - name = track.name; - artists = track.artists; - album = track.album; - durationMs = track.durationMs; - discNumber = track.discNumber; - explicit = track.explicit; - externalIds = track.externalIds; - href = track.href; - isPlayable = track.isPlayable; - linkedFrom = track.linkedFrom; - popularity = track.popularity; - previewUrl = track.previewUrl; - trackNumber = track.trackNumber; - type = track.type; - uri = track.uri; - } + required super.info, + required super.query, + required super.source, + required super.siblings, + required super.sources, + }); static SourcedTrack fromJson( Map json, { @@ -49,110 +29,116 @@ abstract class SourcedTrack extends Track { }) { final preferences = ref.read(userPreferencesProvider); - final sourceInfo = SourceInfo.fromJson(json); - final source = SourceMap.fromJson(json); - final track = Track.fromJson(json); + final info = TrackSourceInfo.fromJson(json["info"]); + final query = TrackSourceQuery.fromJson(json["query"]); + final source = AudioSource.values.firstWhereOrNull( + (source) => source.name == json["source"], + ) ?? + preferences.audioSource; final siblings = (json["siblings"] as List) - .map((sibling) => SourceInfo.fromJson(sibling)) - .toList() - .cast(); + .map((s) => TrackSourceInfo.fromJson(s)) + .toList(); + final sources = + (json["sources"] as List).map((s) => TrackSource.fromJson(s)).toList(); return switch (preferences.audioSource) { AudioSource.youtube => YoutubeSourcedTrack( ref: ref, source: source, siblings: siblings, - sourceInfo: sourceInfo, - track: track, + info: info, + query: query, + sources: sources, ), AudioSource.piped => PipedSourcedTrack( ref: ref, source: source, siblings: siblings, - sourceInfo: sourceInfo, - track: track, + info: info, + query: query, + sources: sources, ), AudioSource.jiosaavn => JioSaavnSourcedTrack( ref: ref, source: source, siblings: siblings, - sourceInfo: sourceInfo, - track: track, + info: info, + query: query, + sources: sources, ), AudioSource.invidious => InvidiousSourcedTrack( ref: ref, source: source, siblings: siblings, - sourceInfo: sourceInfo, - track: track, + info: info, + query: query, + sources: sources, ), }; } - static String getSearchTerm(Track track) { - final artists = - (track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList(); - + static String getSearchTerm(TrackSourceQuery track) { final title = ServiceUtils.getTitle( - track.name!, - artists: artists, + track.title, + artists: track.artists, onlyCleanArtist: true, ).trim(); - return "$title - ${artists.join(", ")}"; + return "$title - ${track.artists.join(", ")}"; } - static Future fetchFromTrack({ - required Track track, + static Future fetchFromQuery({ + required TrackSourceQuery query, required Ref ref, }) async { final preferences = ref.read(userPreferencesProvider); try { return switch (preferences.audioSource) { AudioSource.youtube => - await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref), + await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref), AudioSource.piped => - await PipedSourcedTrack.fetchFromTrack(track: track, ref: ref), + await PipedSourcedTrack.fetchFromTrack(query: query, ref: ref), AudioSource.invidious => - await InvidiousSourcedTrack.fetchFromTrack(track: track, ref: ref), + await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref), AudioSource.jiosaavn => - await JioSaavnSourcedTrack.fetchFromTrack(track: track, ref: ref), + await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref), }; } catch (e) { if (preferences.audioSource == AudioSource.youtube) { rethrow; } - return await YoutubeSourcedTrack.fetchFromTrack(track: track, ref: ref); + return await YoutubeSourcedTrack.fetchFromTrack(query: query, ref: ref); } } static Future> fetchSiblings({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) { final preferences = ref.read(userPreferencesProvider); return switch (preferences.audioSource) { AudioSource.piped => - PipedSourcedTrack.fetchSiblings(track: track, ref: ref), + PipedSourcedTrack.fetchSiblings(query: query, ref: ref), AudioSource.youtube => - YoutubeSourcedTrack.fetchSiblings(track: track, ref: ref), + YoutubeSourcedTrack.fetchSiblings(query: query, ref: ref), AudioSource.jiosaavn => - JioSaavnSourcedTrack.fetchSiblings(track: track, ref: ref), + JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref), AudioSource.invidious => - InvidiousSourcedTrack.fetchSiblings(track: track, ref: ref), + InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref), }; } Future copyWithSibling(); - Future swapWithSibling(SourceInfo sibling); + Future swapWithSibling(TrackSourceInfo sibling); Future swapWithSiblingOfIndex(int index) { return swapWithSibling(siblings[index]); } + Future refreshStream(); String get url { final preferences = ref.read(userPreferencesProvider); @@ -166,10 +152,22 @@ abstract class SourcedTrack extends Track { String getUrlOfCodec(SourceCodecs codec) { final preferences = ref.read(userPreferencesProvider); - return source[codec]?[preferences.audioQuality] ?? - // this will ensure playback doesn't break - source[codec == SourceCodecs.m4a ? SourceCodecs.weba : SourceCodecs.m4a] - [preferences.audioQuality]; + return sources + .firstWhereOrNull( + (source) => + source.codec == codec && + source.quality == preferences.audioQuality, + ) + ?.url ?? + // fallback to the first available source of the same codec + sources.firstWhereOrNull((source) => source.codec == codec)?.url ?? + // fallback to the first available source of any codec + sources + .firstWhereOrNull( + (source) => source.quality == preferences.audioQuality) + ?.url ?? + // fallback to the first available source + sources.first.url; } SourceCodecs get codec { @@ -179,4 +177,12 @@ abstract class SourcedTrack extends Track { ? SourceCodecs.m4a : preferences.streamMusicCodec; } + + TrackSource get activeTrackSource { + final audioQuality = ref.read(userPreferencesProvider).audioQuality; + return sources.firstWhereOrNull( + (source) => source.codec == codec && source.quality == audioQuality, + ) ?? + sources.first; + } } diff --git a/lib/services/sourced_track/sources/invidious.dart b/lib/services/sourced_track/sources/invidious.dart index 4a32ad41..82e001f5 100644 --- a/lib/services/sourced_track/sources/invidious.dart +++ b/lib/services/sourced_track/sources/invidious.dart @@ -1,14 +1,12 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:invidious/invidious.dart'; @@ -24,51 +22,24 @@ final invidiousProvider = Provider( }, ); -class InvidiousSourceInfo extends SourceInfo { - InvidiousSourceInfo({ - required super.id, - required super.title, - required super.artist, - required super.thumbnail, - required super.pageUrl, - required super.duration, - required super.artistUrl, - required super.album, - }); -} - class InvidiousSourcedTrack extends SourcedTrack { InvidiousSourcedTrack({ required super.ref, required super.source, required super.siblings, - required super.sourceInfo, - required super.track, + required super.info, + required super.query, + required super.sources, }); static Future fetchFromTrack({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { - // Indicates a stream url refresh - if (track is InvidiousSourcedTrack) { - final manifest = await ref - .read(invidiousProvider) - .videos - .get(track.sourceInfo.id, local: true); - - return InvidiousSourcedTrack( - ref: ref, - siblings: track.siblings, - source: toSourceMap(manifest), - sourceInfo: track.sourceInfo, - track: track, - ); - } - + final audioSource = ref.read(userPreferencesProvider).audioSource; final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!)) + ..where((s) => s.trackId.equals(query.id)) ..limit(1) ..orderBy([ (s) => @@ -78,14 +49,14 @@ class InvidiousSourcedTrack extends SourcedTrack { final invidiousClient = ref.read(invidiousProvider); if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, track: track); + final siblings = await fetchSiblings(ref: ref, query: query); if (siblings.isEmpty) { - throw TrackNotFoundError(track); + throw TrackNotFoundError(query); } await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: track.id!, + trackId: query.id, sourceId: siblings.first.info.id, sourceType: const Value(SourceType.youtube), ), @@ -94,9 +65,10 @@ class InvidiousSourcedTrack extends SourcedTrack { return InvidiousSourcedTrack( ref: ref, siblings: siblings.map((s) => s.info).skip(1).toList(), - source: siblings.first.source as SourceMap, - sourceInfo: siblings.first.info, - track: track, + sources: siblings.first.source as List, + info: siblings.first.info, + query: query, + source: audioSource, ); } else { final manifest = @@ -105,44 +77,36 @@ class InvidiousSourcedTrack extends SourcedTrack { return InvidiousSourcedTrack( ref: ref, siblings: [], - source: toSourceMap(manifest), - sourceInfo: InvidiousSourceInfo( + sources: toSources(manifest), + info: TrackSourceInfo( id: manifest.videoId, - artist: manifest.author, - artistUrl: manifest.authorUrl, + artists: manifest.author, pageUrl: "https://www.youtube.com/watch?v=${manifest.videoId}", thumbnail: manifest.videoThumbnails.first.url, title: manifest.title, - duration: Duration(seconds: manifest.lengthSeconds), - album: null, + durationMs: Duration(seconds: manifest.lengthSeconds).inMilliseconds, ), - track: track, + query: query, + source: audioSource, ); } } - static SourceMap toSourceMap(InvidiousVideoResponse manifest) { - final m4a = manifest.adaptiveFormats - .where((audio) => audio.type.contains("audio/mp4")) - .sorted((a, b) => int.parse(a.bitrate).compareTo(int.parse(b.bitrate))); - - final weba = manifest.adaptiveFormats - .where((audio) => audio.type.contains("audio/webm")) - .sorted((a, b) => int.parse(a.bitrate).compareTo(int.parse(b.bitrate))); - - return SourceMap( - m4a: SourceQualityMap( - high: m4a.first.url.toString(), - medium: (m4a.elementAtOrNull(m4a.length ~/ 2) ?? m4a[1]).url.toString(), - low: m4a.last.url.toString(), - ), - weba: SourceQualityMap( - high: weba.first.url.toString(), - medium: - (weba.elementAtOrNull(weba.length ~/ 2) ?? weba[1]).url.toString(), - low: weba.last.url.toString(), - ), - ); + static List toSources(InvidiousVideoResponse manifest) { + return manifest.adaptiveFormats.map((stream) { + return TrackSource( + url: stream.url.toString(), + quality: switch (stream.qualityLabel) { + "high" => SourceQualities.high, + "medium" => SourceQualities.medium, + _ => SourceQualities.low, + }, + codec: stream.type.contains("audio/webm") + ? SourceCodecs.weba + : SourceCodecs.m4a, + bitrate: stream.bitrate, + ); + }).toList(); } static Future toSiblingType( @@ -150,22 +114,20 @@ class InvidiousSourcedTrack extends SourcedTrack { YoutubeVideoInfo item, InvidiousClient invidiousClient, ) async { - SourceMap? sourceMap; + List? sourceMap; if (index == 0) { final manifest = await invidiousClient.videos.get(item.id, local: true); - sourceMap = toSourceMap(manifest); + sourceMap = toSources(manifest); } final SiblingType sibling = ( - info: InvidiousSourceInfo( + info: TrackSourceInfo( id: item.id, - artist: item.channelName, - artistUrl: "https://www.youtube.com/${item.channelId}", + artists: item.channelName, pageUrl: "https://www.youtube.com/watch?v=${item.id}", thumbnail: item.thumbnailUrl, title: item.title, - duration: item.duration, - album: null, + durationMs: item.duration.inMilliseconds, ), source: sourceMap, ); @@ -174,20 +136,20 @@ class InvidiousSourcedTrack extends SourcedTrack { } static Future> fetchSiblings({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { final invidiousClient = ref.read(invidiousProvider); final preference = ref.read(userPreferencesProvider); - final query = SourcedTrack.getSearchTerm(track); + final searchQuery = SourcedTrack.getSearchTerm(query); final searchResults = await invidiousClient.search.list( - query, + searchQuery, type: InvidiousSearchType.video, ); - if (ServiceUtils.onlyContainsEnglish(query)) { + if (ServiceUtils.onlyContainsEnglish(searchQuery)) { return await Future.wait( searchResults .whereType() @@ -211,7 +173,7 @@ class InvidiousSourcedTrack extends SourcedTrack { ), ) .toList(), - track, + query, ); return await Future.wait( @@ -224,23 +186,24 @@ class InvidiousSourcedTrack extends SourcedTrack { if (siblings.isNotEmpty) { return this; } - final fetchedSiblings = await fetchSiblings(ref: ref, track: this); + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); return InvidiousSourcedTrack( ref: ref, siblings: fetchedSiblings - .where((s) => s.info.id != sourceInfo.id) + .where((s) => s.info.id != info.id) .map((s) => s.info) .toList(), source: source, - sourceInfo: sourceInfo, - track: this, + info: info, + query: query, + sources: sources, ); } @override - Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id) { + Future swapWithSibling(TrackSourceInfo sibling) async { + if (sibling.id == info.id) { return null; } @@ -251,7 +214,7 @@ class InvidiousSourcedTrack extends SourcedTrack { ? sibling : siblings.firstWhere((s) => s.id == sibling.id); final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, sourceInfo); + ..insert(0, info); final pipedClient = ref.read(invidiousProvider); @@ -261,7 +224,7 @@ class InvidiousSourcedTrack extends SourcedTrack { final database = ref.read(databaseProvider); await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: id!, + trackId: query.id, sourceId: newSourceInfo.id, sourceType: const Value(SourceType.youtube), // Because we're sorting by createdAt in the query @@ -274,9 +237,25 @@ class InvidiousSourcedTrack extends SourcedTrack { return InvidiousSourcedTrack( ref: ref, siblings: newSiblings, - source: toSourceMap(manifest), - sourceInfo: newSourceInfo, - track: this, + sources: toSources(manifest), + info: newSourceInfo, + query: query, + source: source, + ); + } + + @override + Future refreshStream() async { + final manifest = + await ref.read(invidiousProvider).videos.get(info.id, local: true); + + return InvidiousSourcedTrack( + ref: ref, + siblings: siblings, + sources: toSources(manifest), + info: info, + query: query, + source: source, ); } } diff --git a/lib/services/sourced_track/sources/jiosaavn.dart b/lib/services/sourced_track/sources/jiosaavn.dart index 1434e4f7..4b67e717 100644 --- a/lib/services/sourced_track/sources/jiosaavn.dart +++ b/lib/services/sourced_track/sources/jiosaavn.dart @@ -1,49 +1,35 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:jiosaavn/jiosaavn.dart'; import 'package:spotube/extensions/string.dart'; final jiosaavnClient = JioSaavnClient(); -class JioSaavnSourceInfo extends SourceInfo { - JioSaavnSourceInfo({ - required super.id, - required super.title, - required super.artist, - required super.thumbnail, - required super.pageUrl, - required super.duration, - required super.artistUrl, - required super.album, - }); -} - class JioSaavnSourcedTrack extends SourcedTrack { JioSaavnSourcedTrack({ required super.ref, required super.source, required super.siblings, - required super.sourceInfo, - required super.track, + required super.info, + required super.query, + required super.sources, }); static Future fetchFromTrack({ - required Track track, + required TrackSourceQuery query, required Ref ref, bool weakMatch = false, }) async { final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!)) + ..where((s) => s.trackId.equals(query.id)) ..limit(1) ..orderBy([ (s) => @@ -54,15 +40,15 @@ class JioSaavnSourcedTrack extends SourcedTrack { if (cachedSource == null || cachedSource.sourceType != SourceType.jiosaavn) { final siblings = - await fetchSiblings(ref: ref, track: track, weakMatch: weakMatch); + await fetchSiblings(ref: ref, query: query, weakMatch: weakMatch); if (siblings.isEmpty) { - throw TrackNotFoundError(track); + throw TrackNotFoundError(query); } await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: track.id!, + trackId: query.id, sourceId: siblings.first.info.id, sourceType: const Value(SourceType.jiosaavn), ), @@ -71,9 +57,10 @@ class JioSaavnSourcedTrack extends SourcedTrack { return JioSaavnSourcedTrack( ref: ref, siblings: siblings.map((s) => s.info).skip(1).toList(), - source: siblings.first.source!, - sourceInfo: siblings.first.info, - track: track, + sources: siblings.first.source!, + info: siblings.first.info, + query: query, + source: AudioSource.jiosaavn, ); } @@ -85,80 +72,77 @@ class JioSaavnSourcedTrack extends SourcedTrack { return JioSaavnSourcedTrack( ref: ref, siblings: [], - source: source!, - sourceInfo: info, - track: track, + sources: source!, + query: query, + info: info, + source: AudioSource.jiosaavn, ); } static SiblingType toSiblingType(SongResponse result) { final SiblingType sibling = ( - info: JioSaavnSourceInfo( - artist: [ + info: TrackSourceInfo( + artists: [ result.primaryArtists, if (result.featuredArtists.isNotEmpty) ", ", result.featuredArtists ].join("").unescapeHtml(), - artistUrl: - "https://www.jiosaavn.com/artist/${result.primaryArtistsId.split(",").firstOrNull ?? ""}", - duration: Duration(seconds: int.parse(result.duration)), + durationMs: + Duration(seconds: int.parse(result.duration)).inMilliseconds, id: result.id, pageUrl: result.url, thumbnail: result.image?.last.link ?? "", title: result.name!.unescapeHtml(), - album: result.album.name, - ), - source: SourceMap( - m4a: SourceQualityMap( - high: result.downloadUrl! - .firstWhere((element) => element.quality == "320kbps") - .link, - medium: result.downloadUrl! - .firstWhere((element) => element.quality == "160kbps") - .link, - low: result.downloadUrl! - .firstWhere((element) => element.quality == "96kbps") - .link, - ), ), + source: result.downloadUrl!.map((link) { + return TrackSource( + url: link.link, + quality: link.quality == "320kbps" + ? SourceQualities.high + : link.quality == "160kbps" + ? SourceQualities.medium + : SourceQualities.low, + codec: SourceCodecs.m4a, + bitrate: link.quality, + ); + }).toList() ); return sibling; } static Future> fetchSiblings({ - required Track track, + required TrackSourceQuery query, required Ref ref, bool weakMatch = false, }) async { - final query = SourcedTrack.getSearchTerm(track); + final searchQuery = SourcedTrack.getSearchTerm(query); final SongSearchResponse(:results) = - await jiosaavnClient.search.songs(query, limit: 20); + await jiosaavnClient.search.songs(searchQuery, limit: 20); - final trackArtistNames = track.artists?.map((ar) => ar.name).toList(); + final trackArtistNames = query.artists; final matchedResults = results .where( (s) { - s.name?.unescapeHtml().contains(track.name!) ?? false; + s.name?.unescapeHtml().contains(query.title) ?? false; - final sameName = s.name?.unescapeHtml() == track.name; + final sameName = s.name?.unescapeHtml() == query.title; final artistNames = [ s.primaryArtists, if (s.featuredArtists.isNotEmpty) ", ", s.featuredArtists ].join("").unescapeHtml(); final sameArtists = artistNames.split(", ").any( - (artist) => - trackArtistNames?.any((ar) => artist == ar) ?? false, + (artist) => trackArtistNames.any((ar) => artist == ar), ); if (weakMatch) { final containsName = - s.name?.unescapeHtml().contains(track.name!) ?? false; + s.name?.unescapeHtml().contains(query.title) ?? false; final containsPrimaryArtist = s.primaryArtists .unescapeHtml() - .contains(trackArtistNames?.first ?? ""); + .contains(trackArtistNames.first); return containsName && containsPrimaryArtist; } @@ -181,23 +165,24 @@ class JioSaavnSourcedTrack extends SourcedTrack { if (siblings.isNotEmpty) { return this; } - final fetchedSiblings = await fetchSiblings(ref: ref, track: this); + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); return JioSaavnSourcedTrack( ref: ref, siblings: fetchedSiblings - .where((s) => s.info.id != sourceInfo.id) + .where((s) => s.info.id != info.id) .map((s) => s.info) .toList(), source: source, - sourceInfo: sourceInfo, - track: this, + info: info, + query: query, + sources: sources, ); } @override - Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id) { + Future swapWithSibling(TrackSourceInfo sibling) async { + if (sibling.id == this.info.id) { return null; } @@ -208,7 +193,7 @@ class JioSaavnSourcedTrack extends SourcedTrack { ? sibling : siblings.firstWhere((s) => s.id == sibling.id); final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, sourceInfo); + ..insert(0, this.info); final [item] = await jiosaavnClient.songs.detailsById([newSourceInfo.id]); @@ -217,7 +202,7 @@ class JioSaavnSourcedTrack extends SourcedTrack { final database = ref.read(databaseProvider); await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: id!, + trackId: query.id, sourceId: info.id, sourceType: const Value(SourceType.jiosaavn), // Because we're sorting by createdAt in the query @@ -230,9 +215,16 @@ class JioSaavnSourcedTrack extends SourcedTrack { return JioSaavnSourcedTrack( ref: ref, siblings: newSiblings, - source: source!, - sourceInfo: info, - track: this, + sources: source!, + info: info, + query: query, + source: AudioSource.jiosaavn, ); } + + @override + Future refreshStream() async { + // There's no need to refresh the stream for JioSaavnSourcedTrack + return this; + } } diff --git a/lib/services/sourced_track/sources/piped.dart b/lib/services/sourced_track/sources/piped.dart index 7ab9df44..65d613a6 100644 --- a/lib/services/sourced_track/sources/piped.dart +++ b/lib/services/sourced_track/sources/piped.dart @@ -2,15 +2,13 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:piped_client/piped_client.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; @@ -24,48 +22,24 @@ final pipedProvider = Provider( }, ); -class PipedSourceInfo extends SourceInfo { - PipedSourceInfo({ - required super.id, - required super.title, - required super.artist, - required super.thumbnail, - required super.pageUrl, - required super.duration, - required super.artistUrl, - required super.album, - }); -} - class PipedSourcedTrack extends SourcedTrack { PipedSourcedTrack({ required super.ref, required super.source, required super.siblings, - required super.sourceInfo, - required super.track, + required super.info, + required super.query, + required super.sources, }); static Future fetchFromTrack({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { - // Means it wants a refresh of the stream - if (track is PipedSourcedTrack) { - final manifest = - await ref.read(pipedProvider).streams(track.sourceInfo.id); - return PipedSourcedTrack( - ref: ref, - siblings: track.siblings, - sourceInfo: track.sourceInfo, - source: toSourceMap(manifest), - track: track, - ); - } - + final audioSource = ref.read(userPreferencesProvider).audioSource; final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!)) + ..where((s) => s.trackId.equals(query.id)) ..limit(1) ..orderBy([ (s) => @@ -76,14 +50,14 @@ class PipedSourcedTrack extends SourcedTrack { final pipedClient = ref.read(pipedProvider); if (cachedSource == null) { - final siblings = await fetchSiblings(ref: ref, track: track); + final siblings = await fetchSiblings(ref: ref, query: query); if (siblings.isEmpty) { - throw TrackNotFoundError(track); + throw TrackNotFoundError(query); } await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: track.id!, + trackId: query.id, sourceId: siblings.first.info.id, sourceType: Value( preferences.searchMode == SearchMode.youtube @@ -96,9 +70,10 @@ class PipedSourcedTrack extends SourcedTrack { return PipedSourcedTrack( ref: ref, siblings: siblings.map((s) => s.info).skip(1).toList(), - source: siblings.first.source as SourceMap, - sourceInfo: siblings.first.info, - track: track, + source: audioSource, + info: siblings.first.info, + query: query, + sources: siblings.first.source!, ); } else { final manifest = await pipedClient.streams(cachedSource.sourceId); @@ -106,44 +81,36 @@ class PipedSourcedTrack extends SourcedTrack { return PipedSourcedTrack( ref: ref, siblings: [], - source: toSourceMap(manifest), - sourceInfo: PipedSourceInfo( + sources: toSources(manifest), + info: TrackSourceInfo( id: manifest.id, - artist: manifest.uploader, - artistUrl: manifest.uploaderUrl, + artists: manifest.uploader, pageUrl: "https://www.youtube.com/watch?v=${manifest.id}", thumbnail: manifest.thumbnailUrl, title: manifest.title, - duration: manifest.duration, - album: null, + durationMs: manifest.duration.inMilliseconds, ), - track: track, + query: query, + source: audioSource, ); } } - static SourceMap toSourceMap(PipedStreamResponse manifest) { - final m4a = manifest.audioStreams - .where((audio) => audio.format == PipedAudioStreamFormat.m4a) - .sorted((a, b) => a.bitrate.compareTo(b.bitrate)); - - final weba = manifest.audioStreams - .where((audio) => audio.format == PipedAudioStreamFormat.webm) - .sorted((a, b) => a.bitrate.compareTo(b.bitrate)); - - return SourceMap( - m4a: SourceQualityMap( - high: m4a.first.url.toString(), - medium: (m4a.elementAtOrNull(m4a.length ~/ 2) ?? m4a[1]).url.toString(), - low: m4a.last.url.toString(), - ), - weba: SourceQualityMap( - high: weba.first.url.toString(), - medium: - (weba.elementAtOrNull(weba.length ~/ 2) ?? weba[1]).url.toString(), - low: weba.last.url.toString(), - ), - ); + static List toSources(PipedStreamResponse manifest) { + return manifest.audioStreams.map((audio) { + return TrackSource( + url: audio.url.toString(), + quality: switch (audio.quality) { + "high" => SourceQualities.high, + "medium" => SourceQualities.medium, + _ => SourceQualities.low, + }, + codec: audio.format == PipedAudioStreamFormat.m4a + ? SourceCodecs.m4a + : SourceCodecs.weba, + bitrate: audio.bitrate.toString(), + ); + }).toList(); } static Future toSiblingType( @@ -151,40 +118,38 @@ class PipedSourcedTrack extends SourcedTrack { YoutubeVideoInfo item, PipedClient pipedClient, ) async { - SourceMap? sourceMap; + List? sources; if (index == 0) { final manifest = await pipedClient.streams(item.id); - sourceMap = toSourceMap(manifest); + sources = toSources(manifest); } final SiblingType sibling = ( - info: PipedSourceInfo( + info: TrackSourceInfo( id: item.id, - artist: item.channelName, - artistUrl: "https://www.youtube.com/${item.channelId}", + artists: item.channelName, pageUrl: "https://www.youtube.com/watch?v=${item.id}", thumbnail: item.thumbnailUrl, title: item.title, - duration: item.duration, - album: null, + durationMs: item.duration.inMilliseconds, ), - source: sourceMap, + source: sources, ); return sibling; } static Future> fetchSiblings({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { final pipedClient = ref.read(pipedProvider); final preference = ref.read(userPreferencesProvider); - final query = SourcedTrack.getSearchTerm(track); + final searchQuery = SourcedTrack.getSearchTerm(query); final PipedSearchResult(items: searchResults) = await pipedClient.search( - query, + searchQuery, preference.searchMode == SearchMode.youtube ? PipedFilter.videos : PipedFilter.musicSongs, @@ -196,8 +161,7 @@ class PipedSourcedTrack extends SourcedTrack { : preference.searchMode == SearchMode.youtubeMusic; if (isYouTubeMusic) { - final artists = - (track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList(); + final artists = query.artists; return await Future.wait( searchResults @@ -218,7 +182,7 @@ class PipedSourcedTrack extends SourcedTrack { ); } - if (ServiceUtils.onlyContainsEnglish(query)) { + if (ServiceUtils.onlyContainsEnglish(searchQuery)) { return await Future.wait( searchResults .whereType() @@ -241,7 +205,7 @@ class PipedSourcedTrack extends SourcedTrack { ), ) .toList(), - track, + query, ); return await Future.wait( @@ -254,23 +218,24 @@ class PipedSourcedTrack extends SourcedTrack { if (siblings.isNotEmpty) { return this; } - final fetchedSiblings = await fetchSiblings(ref: ref, track: this); + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); return PipedSourcedTrack( ref: ref, siblings: fetchedSiblings - .where((s) => s.info.id != sourceInfo.id) + .where((s) => s.info.id != info.id) .map((s) => s.info) .toList(), source: source, - sourceInfo: sourceInfo, - track: this, + info: info, + query: query, + sources: sources, ); } @override - Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id) { + Future swapWithSibling(TrackSourceInfo sibling) async { + if (sibling.id == info.id) { return null; } @@ -281,7 +246,7 @@ class PipedSourcedTrack extends SourcedTrack { ? sibling : siblings.firstWhere((s) => s.id == sibling.id); final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, sourceInfo); + ..insert(0, info); final pipedClient = ref.read(pipedProvider); @@ -290,7 +255,7 @@ class PipedSourcedTrack extends SourcedTrack { final database = ref.read(databaseProvider); await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: id!, + trackId: query.id, sourceId: newSourceInfo.id, sourceType: const Value(SourceType.youtube), // Because we're sorting by createdAt in the query @@ -303,9 +268,23 @@ class PipedSourcedTrack extends SourcedTrack { return PipedSourcedTrack( ref: ref, siblings: newSiblings, - source: toSourceMap(manifest), - sourceInfo: newSourceInfo, - track: this, + sources: toSources(manifest), + info: info, + query: query, + source: source, + ); + } + + @override + Future refreshStream() async { + final manifest = await ref.read(pipedProvider).streams(info.id); + return PipedSourcedTrack( + ref: ref, + siblings: siblings, + info: info, + source: source, + query: query, + sources: toSources(manifest), ); } } diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 193bdc0d..f55e4337 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -1,16 +1,15 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:spotube/services/song_link/song_link.dart'; import 'package:spotube/services/sourced_track/enums.dart'; import 'package:spotube/services/sourced_track/exceptions.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; -import 'package:spotube/services/sourced_track/models/source_map.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/service_utils.dart'; @@ -21,54 +20,24 @@ final officialMusicRegex = RegExp( caseSensitive: false, ); -class YoutubeSourceInfo extends SourceInfo { - YoutubeSourceInfo({ - required super.id, - required super.title, - required super.artist, - required super.thumbnail, - required super.pageUrl, - required super.duration, - required super.artistUrl, - required super.album, - }); -} - class YoutubeSourcedTrack extends SourcedTrack { YoutubeSourcedTrack({ required super.source, required super.siblings, - required super.sourceInfo, - required super.track, + required super.info, + required super.query, + required super.sources, required super.ref, }); static Future fetchFromTrack({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { - // Indicates the track is requesting a stream refresh - if (track is YoutubeSourcedTrack) { - final manifest = await ref - .read(youtubeEngineProvider) - .getStreamManifest(track.sourceInfo.id); - - final sourcedTrack = YoutubeSourcedTrack( - ref: ref, - siblings: track.siblings, - source: toSourceMap(manifest), - sourceInfo: track.sourceInfo, - track: track, - ); - - AppLogger.log.i("Refreshing ${track.name}: ${sourcedTrack.url}"); - - return sourcedTrack; - } - + final audioSource = ref.read(userPreferencesProvider).audioSource; final database = ref.read(databaseProvider); final cachedSource = await (database.select(database.sourceMatchTable) - ..where((s) => s.trackId.equals(track.id!)) + ..where((s) => s.trackId.equals(query.id)) ..limit(1) ..orderBy([ (s) => @@ -78,14 +47,14 @@ class YoutubeSourcedTrack extends SourcedTrack { .then((s) => s.firstOrNull); if (cachedSource == null || cachedSource.sourceType != SourceType.youtube) { - final siblings = await fetchSiblings(ref: ref, track: track); + final siblings = await fetchSiblings(ref: ref, query: query); if (siblings.isEmpty) { - throw TrackNotFoundError(track); + throw TrackNotFoundError(query); } await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: track.id!, + trackId: query.id, sourceId: siblings.first.info.id, sourceType: const Value(SourceType.youtube), ), @@ -94,9 +63,10 @@ class YoutubeSourcedTrack extends SourcedTrack { return YoutubeSourcedTrack( ref: ref, siblings: siblings.map((s) => s.info).skip(1).toList(), - source: siblings.first.source as SourceMap, - sourceInfo: siblings.first.info, - track: track, + info: siblings.first.info, + source: audioSource, + sources: siblings.first.source ?? [], + query: query, ); } final (item, manifest) = await ref @@ -106,26 +76,25 @@ class YoutubeSourcedTrack extends SourcedTrack { final sourcedTrack = YoutubeSourcedTrack( ref: ref, siblings: [], - source: toSourceMap(manifest), - sourceInfo: YoutubeSourceInfo( + sources: toTrackSources(manifest), + info: TrackSourceInfo( id: item.id.value, - artist: item.author, - artistUrl: "https://www.youtube.com/channel/${item.channelId}", + artists: item.author, pageUrl: item.url, thumbnail: item.thumbnails.highResUrl, title: item.title, - duration: item.duration ?? Duration.zero, - album: null, + durationMs: item.duration?.inMilliseconds ?? 0, ), - track: track, + query: query, + source: audioSource, ); - AppLogger.log.i("${track.name}: ${sourcedTrack.url}"); + AppLogger.log.i("${query.title}: ${sourcedTrack.url}"); return sourcedTrack; } - static SourceMap toSourceMap(StreamManifest manifest) { + static List toTrackSources(StreamManifest manifest) { var m4a = manifest.audioOnly .where((audio) => audio.codec.mimeType == "audio/mp4") .sortByBitrate(); @@ -137,19 +106,20 @@ class YoutubeSourcedTrack extends SourcedTrack { m4a = m4a.isEmpty ? weba.toList() : m4a; weba = weba.isEmpty ? m4a.toList() : weba; - return SourceMap( - m4a: SourceQualityMap( - high: m4a.first.url.toString(), - medium: (m4a.elementAtOrNull(m4a.length ~/ 2) ?? m4a[1]).url.toString(), - low: m4a.last.url.toString(), - ), - weba: SourceQualityMap( - high: weba.first.url.toString(), - medium: - (weba.elementAtOrNull(weba.length ~/ 2) ?? weba[1]).url.toString(), - low: weba.last.url.toString(), - ), - ); + return manifest.audioOnly.map((streamInfo) { + return TrackSource( + url: streamInfo.url.toString(), + quality: streamInfo.qualityLabel == "AUDIO_QUALITY_HIGH" + ? SourceQualities.high + : streamInfo.qualityLabel == "AUDIO_QUALITY_MEDIUM" + ? SourceQualities.medium + : SourceQualities.low, + codec: streamInfo.codec.mimeType == "audio/mp4" + ? SourceCodecs.m4a + : SourceCodecs.weba, + bitrate: streamInfo.bitrate.bitsPerSecond.toString(), + ); + }).toList(); } static Future toSiblingType( @@ -158,23 +128,21 @@ class YoutubeSourcedTrack extends SourcedTrack { dynamic ref, ) async { assert(ref is WidgetRef || ref is Ref, "Invalid ref type"); - SourceMap? sourceMap; + List? sourceMap; if (index == 0) { final manifest = await ref.read(youtubeEngineProvider).getStreamManifest(item.id); - sourceMap = toSourceMap(manifest); + sourceMap = toTrackSources(manifest); } final SiblingType sibling = ( - info: YoutubeSourceInfo( + info: TrackSourceInfo( id: item.id, - artist: item.channelName, - artistUrl: "https://www.youtube.com/channel/${item.channelId}", + artists: item.channelName, pageUrl: "https://www.youtube.com/watch?v=${item.id}", thumbnail: item.thumbnailUrl, title: item.title, - duration: item.duration, - album: null, + durationMs: item.duration.inMilliseconds, ), source: sourceMap, ); @@ -183,16 +151,13 @@ class YoutubeSourcedTrack extends SourcedTrack { } static List rankResults( - List results, Track track) { - final artists = - (track.artists ?? []).map((ar) => ar.name).toList().nonNulls.toList(); - + List results, TrackSourceQuery track) { return results .sorted((a, b) => b.views.compareTo(a.views)) .map((sibling) { int score = 0; - for (final artist in artists) { + for (final artist in track.artists) { final isSameChannelArtist = sibling.channelName.toLowerCase() == artist.toLowerCase(); final channelContainsArtist = sibling.channelName @@ -212,7 +177,7 @@ class YoutubeSourcedTrack extends SourcedTrack { } final titleContainsTrackName = - sibling.title.toLowerCase().contains(track.name!.toLowerCase()); + sibling.title.toLowerCase().contains(track.title.toLowerCase()); final hasOfficialFlag = officialMusicRegex.hasMatch(sibling.title.toLowerCase()); @@ -237,12 +202,12 @@ class YoutubeSourcedTrack extends SourcedTrack { } static Future> fetchFromIsrc({ - required Track track, + required TrackSourceQuery track, required Ref ref, }) async { final isrcResults = []; - final isrc = track.externalIds?.isrc; - if (isrc != null && isrc.isNotEmpty) { + final isrc = track.isrc; + if (isrc.isNotEmpty) { final searchedVideos = await ref.read(youtubeEngineProvider).searchVideos(isrc.toString()); if (searchedVideos.isNotEmpty) { @@ -254,15 +219,18 @@ class YoutubeSourcedTrack extends SourcedTrack { .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') .split(RegExp(r'\p{Z}+', unicode: true)) .where((item) => item.isNotEmpty); - final spWords = track.name! + final spWords = track.title .toLowerCase() .replaceAll(RegExp(r'[^\p{L}\p{N}\p{Z}]+', unicode: true), '') .split(RegExp(r'\p{Z}+', unicode: true)) .where((item) => item.isNotEmpty); // Single word and duration match with 3 second tolerance if (ytWords.any((word) => spWords.contains(word)) && - (videoInfo.duration - track.duration!) - .abs().inMilliseconds <= 3000) { + (videoInfo.duration - + Duration(milliseconds: track.durationMs)) + .abs() + .inMilliseconds <= + 3000) { return videoInfo; } return null; @@ -275,21 +243,21 @@ class YoutubeSourcedTrack extends SourcedTrack { } static Future> fetchSiblings({ - required Track track, + required TrackSourceQuery query, required Ref ref, }) async { final videoResults = []; - if (track is! SourcedTrack) { + if (query is! SourcedTrack) { final isrcResults = await fetchFromIsrc( - track: track, + track: query, ref: ref, ); videoResults.addAll(isrcResults); if (isrcResults.isEmpty) { - final links = await SongLinkService.links(track.id!); + final links = await SongLinkService.links(query.id); final ytLink = links.firstWhereOrNull( (link) => link.platform == "youtube", ); @@ -308,18 +276,18 @@ class YoutubeSourcedTrack extends SourcedTrack { } } - final query = SourcedTrack.getSearchTerm(track); + final searchQuery = SourcedTrack.getSearchTerm(query); final searchResults = - await ref.read(youtubeEngineProvider).searchVideos(query); + await ref.read(youtubeEngineProvider).searchVideos(searchQuery); - if (ServiceUtils.onlyContainsEnglish(query)) { + if (ServiceUtils.onlyContainsEnglish(searchQuery)) { videoResults .addAll(searchResults.map(YoutubeVideoInfo.fromVideo).toList()); } else { videoResults.addAll(rankResults( searchResults.map(YoutubeVideoInfo.fromVideo).toList(), - track, + query, )); } @@ -338,8 +306,8 @@ class YoutubeSourcedTrack extends SourcedTrack { } @override - Future swapWithSibling(SourceInfo sibling) async { - if (sibling.id == sourceInfo.id) { + Future swapWithSibling(TrackSourceInfo sibling) async { + if (sibling.id == info.id) { return null; } @@ -350,7 +318,7 @@ class YoutubeSourcedTrack extends SourcedTrack { ? sibling : siblings.firstWhere((s) => s.id == sibling.id); final newSiblings = siblings.where((s) => s.id != sibling.id).toList() - ..insert(0, sourceInfo); + ..insert(0, info); final manifest = await ref .read(youtubeEngineProvider) @@ -360,7 +328,7 @@ class YoutubeSourcedTrack extends SourcedTrack { await database.into(database.sourceMatchTable).insert( SourceMatchTableCompanion.insert( - trackId: id!, + trackId: query.id, sourceId: newSourceInfo.id, sourceType: const Value(SourceType.youtube), // Because we're sorting by createdAt in the query @@ -372,10 +340,11 @@ class YoutubeSourcedTrack extends SourcedTrack { return YoutubeSourcedTrack( ref: ref, + source: source, siblings: newSiblings, - source: toSourceMap(manifest), - sourceInfo: newSourceInfo, - track: this, + sources: toTrackSources(manifest), + info: info, + query: query, ); } @@ -384,17 +353,37 @@ class YoutubeSourcedTrack extends SourcedTrack { if (siblings.isNotEmpty) { return this; } - final fetchedSiblings = await fetchSiblings(ref: ref, track: this); + final fetchedSiblings = await fetchSiblings(ref: ref, query: query); return YoutubeSourcedTrack( ref: ref, siblings: fetchedSiblings - .where((s) => s.info.id != sourceInfo.id) + .where((s) => s.info.id != info.id) .map((s) => s.info) .toList(), source: source, - sourceInfo: sourceInfo, - track: this, + sources: sources, + info: info, + query: query, ); } + + @override + Future refreshStream() async { + final manifest = + await ref.read(youtubeEngineProvider).getStreamManifest(info.id); + + final sourcedTrack = YoutubeSourcedTrack( + ref: ref, + siblings: siblings, + source: source, + sources: toTrackSources(manifest), + info: info, + query: query, + ); + + AppLogger.log.i("Refreshing ${query.title}: ${sourcedTrack.url}"); + + return sourcedTrack; + } } diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index 72a98a0b..ccff62b0 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -195,10 +195,9 @@ abstract class ServiceUtils { @Deprecated("In favor spotify lyrics api, this isn't needed anymore") static Future getTimedLyrics(SourcedTrack track) async { - final artistNames = - track.artists?.map((artist) => artist.name!).toList() ?? []; + final artistNames = track.query.artists; final query = getTitle( - track.name!, + track.query.title, artists: artistNames, ); @@ -217,13 +216,11 @@ abstract class ServiceUtils { final rateSortedResults = results.map((result) { final title = result.text.trim().toLowerCase(); int points = 0; - final hasAllArtists = track.artists - ?.map((artist) => artist.name!) - .every((artist) => title.contains(artist.toLowerCase())) ?? - false; - final hasTrackName = title.contains(track.name!.toLowerCase()); + final hasAllArtists = track.query.artists + .every((artist) => title.contains(artist.toLowerCase())); + final hasTrackName = title.contains(track.query.title.toLowerCase()); final isNotLive = !PrimitiveUtils.containsTextInBracket(title, "live"); - final exactYtMatch = title == track.sourceInfo.title.toLowerCase(); + final exactYtMatch = title == track.info.title.toLowerCase(); if (exactYtMatch) points = 7; for (final criteria in [hasTrackName, hasAllArtists, isNotLive]) { if (criteria) points++; From 5f47dc3d6db3d8bbc21cf65866556f09195e8a7f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 14:42:29 +0600 Subject: [PATCH 22/60] refactor: remove old spotify.dart types and custom spotube metadata types --- lib/collections/fake.dart | 257 +-- lib/collections/spotify_markets.dart | 2 +- .../dialogs/playlist_add_track_dialog.dart | 30 +- .../dialogs/replace_downloaded_dialog.dart | 6 +- .../dialogs/track_details_dialog.dart | 45 +- lib/components/heart_button/heart_button.dart | 11 +- lib/components/links/artist_link.dart | 11 +- .../presentation_actions.dart | 28 +- .../presentation_props.dart | 15 +- .../presentation_state.dart | 57 +- .../track_presentation/presentation_top.dart | 28 +- .../use_action_callbacks.dart | 30 +- .../use_is_user_playlist.dart | 9 +- .../use_track_tile_play_callback.dart | 22 +- lib/components/track_tile/track_options.dart | 130 +- lib/components/track_tile/track_tile.dart | 41 +- lib/extensions/album_simple.dart | 21 - lib/extensions/artist_simple.dart | 7 - lib/extensions/image.dart | 34 - lib/extensions/track.dart | 107 - lib/hooks/configurators/use_deep_linking.dart | 163 +- .../configurators/use_endless_playback.dart | 2 - lib/models/database/database.dart | 2 +- lib/models/database/database.steps.dart | 2 +- lib/models/database/tables/history.dart | 14 +- lib/models/local_track.dart | 44 - lib/models/metadata/market.dart | 252 +++ lib/models/metadata/metadata.dart | 3 +- lib/models/metadata/metadata.freezed.dart | 499 +---- lib/models/metadata/metadata.g.dart | 35 +- lib/models/metadata/search.dart | 2 +- lib/models/metadata/track.dart | 59 +- lib/models/spotify/home_feed.dart | 247 --- lib/models/spotify/home_feed.freezed.dart | 1776 ----------------- lib/models/spotify/home_feed.g.dart | 165 -- lib/models/spotify/recommendation_seeds.dart | 40 - .../spotify/recommendation_seeds.freezed.dart | 786 -------- .../spotify/recommendation_seeds.g.dart | 44 - lib/models/spotify_friends.dart | 111 -- lib/models/spotify_friends.g.dart | 60 - lib/modules/album/album_card.dart | 22 +- lib/modules/artist/artist_album_list.dart | 2 +- lib/modules/home/sections/featured.dart | 59 +- lib/modules/home/sections/friends.dart | 65 - .../home/sections/friends/friend_item.dart | 117 -- .../home/sections/genres/genre_card.dart | 116 -- .../genres/genre_card_playlist_card.dart | 130 -- lib/modules/home/sections/genres/genres.dart | 139 -- lib/modules/home/sections/made_for_user.dart | 35 - lib/modules/home/sections/new_releases.dart | 16 +- .../local_folder/local_folder_item.dart | 4 +- .../playlist_generate/multi_select_field.dart | 272 --- .../recommendation_attribute_dials.dart | 183 -- .../recommendation_attribute_fields.dart | 182 -- .../seeds_multi_autocomplete.dart | 165 -- .../playlist_generate/simple_track_tile.dart | 45 - .../library/user_downloads/download_item.dart | 30 +- lib/modules/player/player.dart | 67 +- lib/modules/player/player_actions.dart | 17 +- lib/modules/player/player_queue.dart | 12 +- lib/modules/player/player_track_details.dart | 12 +- lib/modules/player/sibling_tracks_sheet.dart | 63 +- lib/modules/playlist/playlist_card.dart | 82 +- .../playlist/playlist_create_dialog.dart | 111 +- lib/modules/root/bottom_player.dart | 13 +- lib/modules/root/sidebar/sidebar_footer.dart | 13 +- lib/modules/stats/common/album_item.dart | 29 +- lib/modules/stats/common/artist_item.dart | 11 +- lib/modules/stats/common/playlist_item.dart | 11 +- lib/modules/stats/common/track_item.dart | 15 +- lib/modules/stats/top/albums.dart | 2 +- lib/modules/stats/top/artists.dart | 12 +- lib/modules/stats/top/tracks.dart | 2 +- lib/pages/album/album.dart | 2 +- lib/pages/artist/artist.dart | 20 +- lib/pages/artist/section/footer.dart | 3 +- lib/pages/artist/section/header.dart | 24 +- lib/pages/artist/section/related_artists.dart | 67 +- lib/pages/artist/section/top_tracks.dart | 16 +- lib/pages/connect/control/control.dart | 9 +- .../getting_started/sections/region.dart | 5 +- lib/pages/home/genres/genre_playlists.dart | 145 -- lib/pages/home/genres/genres.dart | 106 - lib/pages/home/home.dart | 27 +- .../playlist_generate/playlist_generate.dart | 717 ------- .../playlist_generate_result.dart | 272 --- lib/pages/library/user_albums.dart | 3 +- lib/pages/library/user_artists.dart | 3 +- lib/pages/library/user_downloads.dart | 7 +- .../user_local_tracks/local_folder.dart | 13 +- lib/pages/lyrics/lyrics.dart | 10 +- lib/pages/lyrics/plain_lyrics.dart | 6 +- lib/pages/lyrics/synced_lyrics.dart | 6 +- lib/pages/player/lyrics.dart | 8 +- lib/pages/playlist/liked_playlist.dart | 11 +- lib/pages/playlist/playlist.dart | 21 +- lib/pages/profile/profile.dart | 108 +- lib/pages/search/search.dart | 5 +- lib/pages/search/sections/albums.dart | 2 +- lib/pages/search/sections/artists.dart | 2 +- lib/pages/search/sections/playlists.dart | 2 +- lib/pages/search/sections/tracks.dart | 32 +- .../settings/sections/language_region.dart | 2 +- lib/pages/stats/albums/albums.dart | 2 +- lib/pages/stats/artists/artists.dart | 6 +- lib/pages/stats/fees/fees.dart | 6 +- lib/pages/stats/minutes/minutes.dart | 11 +- lib/pages/stats/playlists/playlists.dart | 2 +- lib/pages/stats/streams/streams.dart | 2 +- lib/pages/track/track.dart | 16 +- .../audio_player/audio_player_streams.dart | 5 +- lib/provider/blacklist_provider.dart | 5 +- .../custom_spotify_endpoint_provider.dart | 10 - lib/provider/download_manager_provider.dart | 37 +- lib/provider/history/top/albums.dart | 62 +- lib/provider/history/top/playlists.dart | 58 +- lib/provider/history/top/tracks.dart | 134 +- .../local_tracks/local_tracks_provider.dart | 21 +- lib/provider/{spotify => }/lyrics/synced.dart | 135 +- .../metadata_plugin/album/releases.dart | 29 + .../metadata_plugin/artist/wikipedia.dart | 18 + .../metadata_plugin/library/playlists.dart | 10 +- .../metadata_plugin/playlist/playlist.dart | 129 +- .../metadata_plugin/tracks/track.dart | 16 + .../utils/family_paginated.dart | 10 +- lib/provider/server/track_sources.dart | 5 +- lib/provider/spotify/album/favorite.dart | 91 - lib/provider/spotify/album/is_saved.dart | 12 - lib/provider/spotify/album/releases.dart | 87 - lib/provider/spotify/album/tracks.dart | 63 - lib/provider/spotify/artist/albums.dart | 68 - lib/provider/spotify/artist/artist.dart | 10 - lib/provider/spotify/artist/following.dart | 152 -- lib/provider/spotify/artist/is_following.dart | 12 - lib/provider/spotify/artist/related.dart | 13 - lib/provider/spotify/artist/top_tracks.dart | 16 - lib/provider/spotify/artist/wikipedia.dart | 12 - lib/provider/spotify/category/categories.dart | 21 - lib/provider/spotify/category/genres.dart | 6 - lib/provider/spotify/category/playlists.dart | 73 - lib/provider/spotify/playlist/favorite.dart | 146 -- lib/provider/spotify/playlist/featured.dart | 57 - lib/provider/spotify/playlist/generate.dart | 44 - lib/provider/spotify/playlist/liked.dart | 39 - lib/provider/spotify/playlist/playlist.dart | 173 -- lib/provider/spotify/playlist/tracks.dart | 70 - lib/provider/spotify/search/search.dart | 90 - lib/provider/spotify/spotify.dart | 134 -- lib/provider/spotify/tracks/track.dart | 10 - lib/provider/spotify/user/friends.dart | 7 - lib/provider/spotify/user/me.dart | 6 - lib/provider/spotify/utils/async.dart | 5 - lib/provider/spotify/utils/json_cast.dart | 21 - lib/provider/spotify/utils/mixin.dart | 24 - lib/provider/spotify/utils/provider.dart | 6 - .../spotify/utils/provider/cursor.dart | 56 - .../spotify/utils/provider/cursor_family.dart | 113 -- .../spotify/utils/provider/paginated.dart | 63 - .../utils/provider/paginated_family.dart | 120 -- lib/provider/spotify/utils/state.dart | 56 - lib/provider/spotify/views/home.dart | 20 - lib/provider/spotify/views/home_section.dart | 24 - lib/provider/spotify/views/view.dart | 19 - .../user_preferences_provider.dart | 2 +- lib/services/audio_player/audio_player.dart | 3 +- .../spotify_endpoints.dart | 222 --- lib/services/metadata/endpoints/search.dart | 8 +- lib/utils/service_utils.dart | 47 +- pubspec.lock | 8 - pubspec.yaml | 1 - 170 files changed, 1649 insertions(+), 10215 deletions(-) delete mode 100644 lib/extensions/album_simple.dart delete mode 100644 lib/extensions/artist_simple.dart delete mode 100644 lib/extensions/image.dart delete mode 100644 lib/extensions/track.dart delete mode 100644 lib/models/local_track.dart create mode 100644 lib/models/metadata/market.dart delete mode 100644 lib/models/spotify/home_feed.dart delete mode 100644 lib/models/spotify/home_feed.freezed.dart delete mode 100644 lib/models/spotify/home_feed.g.dart delete mode 100644 lib/models/spotify/recommendation_seeds.dart delete mode 100644 lib/models/spotify/recommendation_seeds.freezed.dart delete mode 100644 lib/models/spotify/recommendation_seeds.g.dart delete mode 100644 lib/models/spotify_friends.dart delete mode 100644 lib/models/spotify_friends.g.dart delete mode 100644 lib/modules/home/sections/friends.dart delete mode 100644 lib/modules/home/sections/friends/friend_item.dart delete mode 100644 lib/modules/home/sections/genres/genre_card.dart delete mode 100644 lib/modules/home/sections/genres/genre_card_playlist_card.dart delete mode 100644 lib/modules/home/sections/genres/genres.dart delete mode 100644 lib/modules/home/sections/made_for_user.dart delete mode 100644 lib/modules/library/playlist_generate/multi_select_field.dart delete mode 100644 lib/modules/library/playlist_generate/recommendation_attribute_dials.dart delete mode 100644 lib/modules/library/playlist_generate/recommendation_attribute_fields.dart delete mode 100644 lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart delete mode 100644 lib/modules/library/playlist_generate/simple_track_tile.dart delete mode 100644 lib/pages/home/genres/genre_playlists.dart delete mode 100644 lib/pages/home/genres/genres.dart delete mode 100644 lib/pages/library/playlist_generate/playlist_generate.dart delete mode 100644 lib/pages/library/playlist_generate/playlist_generate_result.dart delete mode 100644 lib/provider/custom_spotify_endpoint_provider.dart rename lib/provider/{spotify => }/lyrics/synced.dart (54%) create mode 100644 lib/provider/metadata_plugin/album/releases.dart create mode 100644 lib/provider/metadata_plugin/artist/wikipedia.dart create mode 100644 lib/provider/metadata_plugin/tracks/track.dart delete mode 100644 lib/provider/spotify/album/favorite.dart delete mode 100644 lib/provider/spotify/album/is_saved.dart delete mode 100644 lib/provider/spotify/album/releases.dart delete mode 100644 lib/provider/spotify/album/tracks.dart delete mode 100644 lib/provider/spotify/artist/albums.dart delete mode 100644 lib/provider/spotify/artist/artist.dart delete mode 100644 lib/provider/spotify/artist/following.dart delete mode 100644 lib/provider/spotify/artist/is_following.dart delete mode 100644 lib/provider/spotify/artist/related.dart delete mode 100644 lib/provider/spotify/artist/top_tracks.dart delete mode 100644 lib/provider/spotify/artist/wikipedia.dart delete mode 100644 lib/provider/spotify/category/categories.dart delete mode 100644 lib/provider/spotify/category/genres.dart delete mode 100644 lib/provider/spotify/category/playlists.dart delete mode 100644 lib/provider/spotify/playlist/favorite.dart delete mode 100644 lib/provider/spotify/playlist/featured.dart delete mode 100644 lib/provider/spotify/playlist/generate.dart delete mode 100644 lib/provider/spotify/playlist/liked.dart delete mode 100644 lib/provider/spotify/playlist/playlist.dart delete mode 100644 lib/provider/spotify/playlist/tracks.dart delete mode 100644 lib/provider/spotify/search/search.dart delete mode 100644 lib/provider/spotify/spotify.dart delete mode 100644 lib/provider/spotify/tracks/track.dart delete mode 100644 lib/provider/spotify/user/friends.dart delete mode 100644 lib/provider/spotify/user/me.dart delete mode 100644 lib/provider/spotify/utils/async.dart delete mode 100644 lib/provider/spotify/utils/json_cast.dart delete mode 100644 lib/provider/spotify/utils/mixin.dart delete mode 100644 lib/provider/spotify/utils/provider.dart delete mode 100644 lib/provider/spotify/utils/provider/cursor.dart delete mode 100644 lib/provider/spotify/utils/provider/cursor_family.dart delete mode 100644 lib/provider/spotify/utils/provider/paginated.dart delete mode 100644 lib/provider/spotify/utils/provider/paginated_family.dart delete mode 100644 lib/provider/spotify/utils/state.dart delete mode 100644 lib/provider/spotify/views/home.dart delete mode 100644 lib/provider/spotify/views/home_section.dart delete mode 100644 lib/provider/spotify/views/view.dart delete mode 100644 lib/services/custom_spotify_endpoints/spotify_endpoints.dart diff --git a/lib/collections/fake.dart b/lib/collections/fake.dart index 09bb8087..7d201ae2 100644 --- a/lib/collections/fake.dart +++ b/lib/collections/fake.dart @@ -1,19 +1,13 @@ -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/models/spotify/home_feed.dart'; -import 'package:spotube/models/spotify_friends.dart'; import 'package:spotube/provider/history/summary.dart'; abstract class FakeData { - static final Image image = Image() - ..height = 1 - ..width = 1 - ..url = "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg"; - - static final Followers followers = Followers() - ..href = "text" - ..total = 1; + static final SpotubeImageObject image = SpotubeImageObject( + height: 100, + width: 100, + url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg", + ); static final SpotubeFullArtistObject artist = SpotubeFullArtistObject( id: "1", @@ -30,43 +24,26 @@ abstract class FakeData { ], ); - static final externalIds = ExternalIds() - ..isrc = "text" - ..ean = "text" - ..upc = "text"; + static final SpotubeFullAlbumObject album = SpotubeFullAlbumObject( + id: "1", + name: "A good album", + externalUri: "https://example.com", + artists: [artistSimple], + releaseDate: "2021-01-01", + albumType: SpotubeAlbumType.album, + images: [image], + totalTracks: 10, + genres: ["genre"], + recordLabel: "Record Label", + ); - static final externalUrls = ExternalUrls()..spotify = "text"; - - static final Album album = Album() - ..id = "1" - ..genres = ["genre"] - ..label = "label" - ..popularity = 1 - ..albumType = AlbumType.album - // ..artists = [artist] - ..availableMarkets = [Market.BD] - ..externalUrls = externalUrls - ..href = "text" - ..images = [image] - ..name = "Another good album" - ..releaseDate = "2021-01-01" - ..releaseDatePrecision = DatePrecision.day - ..tracks = [track] - ..type = "type" - ..uri = "uri" - ..externalIds = externalIds - ..copyrights = [ - Copyright() - ..type = CopyrightType.C - ..text = "text", - ]; - - static final ArtistSimple artistSimple = ArtistSimple() - ..id = "1" - ..name = "What an artist" - ..type = "type" - ..uri = "uri" - ..externalUrls = externalUrls; + static final SpotubeSimpleArtistObject artistSimple = + SpotubeSimpleArtistObject( + id: "1", + name: "What an artist", + externalUri: "https://example.com", + images: null, + ); static final SpotubeSimpleAlbumObject albumSimple = SpotubeSimpleAlbumObject( albumType: SpotubeAlbumType.album, @@ -84,163 +61,51 @@ abstract class FakeData { ], ); - static final Track track = Track() - ..id = "1" - // ..artists = [artist, artist, artist] - // ..album = albumSimple - ..availableMarkets = [Market.BD] - ..discNumber = 1 - ..durationMs = 50000 - ..explicit = false - ..externalUrls = externalUrls - ..href = "text" - ..name = "A Track Name" - ..popularity = 1 - ..previewUrl = "url" - ..trackNumber = 1 - ..type = "type" - ..uri = "uri" - ..externalIds = externalIds - ..isPlayable = true - ..explicit = false - ..linkedFrom = trackLink; - - static final simpleTrack = SpotubeSimpleTrackObject( + static final SpotubeFullTrackObject track = SpotubeTrackObject.full( id: "1", - name: "A Track Name", - artists: [], - album: albumSimple, + name: "A good track", externalUri: "https://example.com", - durationMs: 50000, + album: albumSimple, + durationMs: 3 * 60 * 1000, // 3 minutes + isrc: "USUM72112345", explicit: false, + ) as SpotubeFullTrackObject; + + static final SpotubeUserObject user = SpotubeUserObject( + id: "1", + name: "User Name", + externalUri: "https://example.com", + images: [image], ); - static final TrackLink trackLink = TrackLink() - ..id = "1" - ..type = "type" - ..uri = "uri" - ..externalUrls = {"spotify": "text"} - ..href = "text"; + static final SpotubeFullPlaylistObject playlist = SpotubeFullPlaylistObject( + id: "1", + name: "A good playlist", + description: "A very good playlist description", + externalUri: "https://example.com", + collaborative: false, + public: true, + owner: user, + images: [image], + collaborators: [user]); - static final Paging paging = Paging() - ..href = "text" - ..itemsNative = [track.toJson()] - ..limit = 1 - ..next = "text" - ..offset = 1 - ..previous = "text" - ..total = 1; - - static final User user = User() - ..id = "1" - ..displayName = "Your Name" - ..birthdate = "2021-01-01" - ..country = Market.BD - ..email = "test@email.com" - ..followers = followers - ..href = "text" - ..images = [image] - ..type = "type" - ..uri = "uri"; - - static final TracksLink tracksLink = TracksLink() - ..href = "text" - ..total = 1; - - static final Playlist playlist = Playlist() - ..id = "1" - ..collaborative = false - ..description = "A very good playlist description" - ..externalUrls = externalUrls - ..followers = followers - ..href = "text" - ..images = [image] - ..name = "A good playlist" - ..owner = user - ..public = true - ..snapshotId = "text" - ..tracks = paging - ..tracksLink = tracksLink - ..type = "type" - ..uri = "uri"; - - static final PlaylistSimple playlistSimple = PlaylistSimple() - ..id = "1" - ..collaborative = false - ..externalUrls = externalUrls - ..href = "text" - ..images = [image] - ..name = "A good playlist" - ..owner = user - ..public = true - ..snapshotId = "text" - ..tracksLink = tracksLink - ..type = "type" - ..description = "A very good playlist description" - ..uri = "uri"; - - static final Category category = Category() - ..href = "text" - ..icons = [image] - ..id = "1" - ..name = "category"; - - static final friends = SpotifyFriends( - friends: [ - for (var i = 0; i < 3; i++) - SpotifyFriendActivity( - user: const SpotifyFriend( - name: "name", - imageUrl: "imageUrl", - uri: "uri", - ), - track: SpotifyActivityTrack( - name: "name", - artist: const SpotifyActivityArtist( - name: "name", - uri: "uri", - ), - album: const SpotifyActivityAlbum( - name: "name", - uri: "uri", - ), - context: SpotifyActivityContext( - name: "name", - index: i, - uri: "uri", - ), - imageUrl: "imageUrl", - uri: "uri", - ), - ), - ], + static final SpotubeSimplePlaylistObject playlistSimple = + SpotubeSimplePlaylistObject( + id: "1", + name: "A good playlist", + description: "A very good playlist description", + externalUri: "https://example.com", + owner: user, + images: [image], ); - static final feedSection = SpotifyHomeFeedSection( - typename: "HomeGenericSectionData", - uri: "spotify:section:lol", - title: "Dummy", - items: [ - for (int i = 0; i < 10; i++) - SpotifyHomeFeedSectionItem( - typename: "PlaylistResponseWrapper", - playlist: SpotifySectionPlaylist( - name: "Playlist $i", - description: "Really super important description $i", - format: "daily-mix", - images: [ - const SpotifySectionItemImage( - height: 1, - width: 1, - url: "https://dummyimage.com/100x100/cfcfcf/cfcfcf.jpg", - ), - ], - owner: "Spotify", - uri: "spotify:playlist:id", - ), - ) - ], - ); + static final SpotubeBrowseSectionObject browseSection = + SpotubeBrowseSectionObject( + id: "section-id", + title: "Browse Section", + browseMore: true, + externalUri: "https://example.com/browse/section", + items: [playlistSimple, playlistSimple, playlistSimple]); static const historySummary = PlaybackHistorySummary( albums: 1, diff --git a/lib/collections/spotify_markets.dart b/lib/collections/spotify_markets.dart index 514b3f0b..c4788022 100644 --- a/lib/collections/spotify_markets.dart +++ b/lib/collections/spotify_markets.dart @@ -1,6 +1,6 @@ // Country Codes contributed by momobobe -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/market.dart'; final spotifyMarkets = [ (Market.AL, "Albania (AL)"), diff --git a/lib/components/dialogs/playlist_add_track_dialog.dart b/lib/components/dialogs/playlist_add_track_dialog.dart index 5098bf9d..8bdd24bd 100644 --- a/lib/components/dialogs/playlist_add_track_dialog.dart +++ b/lib/components/dialogs/playlist_add_track_dialog.dart @@ -1,18 +1,18 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; class PlaylistAddTrackDialog extends HookConsumerWidget { /// The id of the playlist this dialog was opened from final String? openFromPlaylist; - final List tracks; + final List tracks; const PlaylistAddTrackDialog({ required this.tracks, required this.openFromPlaylist, @@ -22,24 +22,23 @@ class PlaylistAddTrackDialog extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final typography = Theme.of(context).typography; - final userPlaylists = ref.watch(favoritePlaylistsProvider); + final userPlaylists = ref.watch(metadataPluginSavedPlaylistsProvider); final favoritePlaylistsNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); - final me = ref.watch(meProvider); + final me = ref.watch(metadataPluginUserProvider); final filteredPlaylists = useMemoized( () => userPlaylists.asData?.value.items .where( (playlist) => - playlist.owner?.id != null && - playlist.owner!.id == me.asData?.value.id && + playlist.owner.id == me.asData?.value?.id && playlist.id != openFromPlaylist, ) .toList() ?? [], - [userPlaylists.asData?.value, me.asData?.value.id, openFromPlaylist], + [userPlaylists.asData?.value, me.asData?.value?.id, openFromPlaylist], ); final playlistsCheck = useState({}); @@ -60,7 +59,7 @@ class PlaylistAddTrackDialog extends HookConsumerWidget { selectedPlaylists.map( (playlistId) => favoritePlaylistsNotifier.addTracks( playlistId, - tracks.map((e) => e.id!).toList(), + tracks.map((e) => e.id).toList(), ), ), ).then((_) => context.mounted ? Navigator.pop(context, true) : null); @@ -109,8 +108,7 @@ class PlaylistAddTrackDialog extends HookConsumerWidget { }, ), leading: Avatar( - initials: - Avatar.getInitials(playlist.name ?? "Playlist"), + initials: Avatar.getInitials(playlist.name), provider: UniversalImage.imageProvider( playlist.images.asUrlString( placeholder: ImagePlaceholder.collection, @@ -124,20 +122,20 @@ class PlaylistAddTrackDialog extends HookConsumerWidget { onChanged: (val) { playlistsCheck.value = { ...playlistsCheck.value, - playlist.id!: val == CheckboxState.checked, + playlist.id: val == CheckboxState.checked, }; }, ), onPressed: () { playlistsCheck.value = { ...playlistsCheck.value, - playlist.id!: + playlist.id: !(playlistsCheck.value[playlist.id] ?? false), }; }, child: Padding( padding: const EdgeInsets.only(left: 8.0), - child: Text(playlist.name!), + child: Text(playlist.name), ), ); }, diff --git a/lib/components/dialogs/replace_downloaded_dialog.dart b/lib/components/dialogs/replace_downloaded_dialog.dart index 3a0f3a1d..6634a039 100644 --- a/lib/components/dialogs/replace_downloaded_dialog.dart +++ b/lib/components/dialogs/replace_downloaded_dialog.dart @@ -1,13 +1,13 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; final replaceDownloadedFileState = StateProvider((ref) => null); class ReplaceDownloadedDialog extends ConsumerWidget { - final Track track; + final SpotubeTrackObject track; const ReplaceDownloadedDialog({required this.track, super.key}); @override @@ -16,7 +16,7 @@ class ReplaceDownloadedDialog extends ConsumerWidget { final replaceAll = ref.watch(replaceDownloadedFileState); return AlertDialog( - title: Text(context.l10n.track_exists(track.name ?? "")), + title: Text(context.l10n.track_exists(track.name)), content: RadioGroup( value: groupValue, onChanged: (value) { diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index 7237afc6..f916aefb 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -1,32 +1,34 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/hyper_link.dart'; -import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/extensions/duration.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; +import 'package:spotube/provider/server/track_sources.dart'; +import 'package:spotube/services/sourced_track/sourced_track.dart'; -class TrackDetailsDialog extends HookWidget { - final Track track; +class TrackDetailsDialog extends HookConsumerWidget { + final SpotubeFullTrackObject track; const TrackDetailsDialog({ super.key, required this.track, }); @override - Widget build(BuildContext context) { + Widget build(BuildContext context, ref) { final theme = Theme.of(context); final mediaQuery = MediaQuery.of(context); + final sourcedTrack = + ref.read(trackSourcesProvider(TrackSourceQuery.fromTrack(track))); final detailsMap = { - context.l10n.title: track.name!, + context.l10n.title: track.name, context.l10n.artist: ArtistLink( - artists: track.artists ?? [], + artists: track.artists, mainAxisAlignment: WrapAlignment.start, textStyle: const TextStyle(color: Colors.blue), hideOverflowArtist: false, @@ -37,17 +39,15 @@ class TrackDetailsDialog extends HookWidget { // overflow: TextOverflow.ellipsis, // style: const TextStyle(color: Colors.blue), // ), - context.l10n.duration: (track is SourcedTrack - ? (track as SourcedTrack).sourceInfo.duration - : track.duration!) - .toHumanReadableString(), - if (track.album!.releaseDate != null) - context.l10n.released: track.album!.releaseDate, - context.l10n.popularity: track.popularity?.toString() ?? "0", + context.l10n.duration: sourcedTrack.asData != null + ? Duration(milliseconds: sourcedTrack.asData!.value.info.durationMs) + .toHumanReadableString() + : Duration(milliseconds: track.durationMs).toHumanReadableString(), + if (track.album.releaseDate != null) + context.l10n.released: track.album.releaseDate, }; - final sourceInfo = - track is SourcedTrack ? (track as SourcedTrack).sourceInfo : null; + final sourceInfo = sourcedTrack.asData?.value.info; final ytTracksDetailsMap = sourceInfo == null ? {} @@ -58,12 +58,7 @@ class TrackDetailsDialog extends HookWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - context.l10n.channel: Hyperlink( - sourceInfo.artist, - sourceInfo.artistUrl, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + context.l10n.channel: Text(sourceInfo.artists), context.l10n.streamUrl: Hyperlink( (track as SourcedTrack).url, (track as SourcedTrack).url, diff --git a/lib/components/heart_button/heart_button.dart b/lib/components/heart_button/heart_button.dart index 275d5db1..fa9508f0 100644 --- a/lib/components/heart_button/heart_button.dart +++ b/lib/components/heart_button/heart_button.dart @@ -1,11 +1,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/heart_button/use_track_toggle_like.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; class HeartButton extends HookConsumerWidget { final bool isLiked; @@ -63,7 +64,7 @@ class HeartButton extends HookConsumerWidget { } class TrackHeartButton extends HookConsumerWidget { - final Track track; + final SpotubeTrackObject track; const TrackHeartButton({ super.key, required this.track, @@ -71,8 +72,8 @@ class TrackHeartButton extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final savedTracks = ref.watch(likedTracksProvider); - final me = ref.watch(meProvider); + final savedTracks = ref.watch(metadataPluginSavedTracksProvider); + final me = ref.watch(metadataPluginUserProvider); final (:isLiked, :toggleTrackLike) = useTrackToggleLike(track, ref); if (me.isLoading) { diff --git a/lib/components/links/artist_link.dart b/lib/components/links/artist_link.dart index 9467cb38..dc093345 100644 --- a/lib/components/links/artist_link.dart +++ b/lib/components/links/artist_link.dart @@ -1,12 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/links/anchor_button.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class ArtistLink extends StatelessWidget { - final List artists; + final List artists; final WrapCrossAlignment crossAxisAlignment; final WrapAlignment mainAxisAlignment; final TextStyle textStyle; @@ -38,19 +38,16 @@ class ArtistLink extends StatelessWidget { .entries .map( (artist) => Builder(builder: (context) { - if (artist.value.name == null) { - return Text("Spotify", style: textStyle); - } return AnchorButton( (artist.key != artists.length - 1) ? "${artist.value.name}, " - : artist.value.name!, + : artist.value.name, onTap: () { if (onRouteChange != null) { onRouteChange?.call("/artist/${artist.value.id}"); } else { context - .navigateTo(ArtistRoute(artistId: artist.value.id!)); + .navigateTo(ArtistRoute(artistId: artist.value.id)); } }, overflow: TextOverflow.ellipsis, diff --git a/lib/components/track_presentation/presentation_actions.dart b/lib/components/track_presentation/presentation_actions.dart index bbeb90a5..197d3dca 100644 --- a/lib/components/track_presentation/presentation_actions.dart +++ b/lib/components/track_presentation/presentation_actions.dart @@ -1,7 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart'; import 'package:spotube/components/dialogs/confirm_download_dialog.dart'; @@ -10,6 +9,7 @@ import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/presentation_state.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -76,9 +76,11 @@ class TrackPresentationActionsSection extends HookConsumerWidget { Future actionDownloadTracks({ required BuildContext context, - required List tracks, + required List tracks, required String action, }) async { + final fullTrackObjects = + tracks.whereType().toList(); final confirmed = audioSource == AudioSource.piped || (await showDialog( context: context, @@ -88,10 +90,10 @@ class TrackPresentationActionsSection extends HookConsumerWidget { ) ?? false); if (confirmed != true) return; - downloader.batchAddToQueue(tracks); + downloader.batchAddToQueue(fullTrackObjects); notifier.deselectAllTracks(); if (!context.mounted) return; - showToastForAction(context, action, tracks.length); + showToastForAction(context, action, fullTrackObjects.length); } return AdaptivePopSheetList( @@ -143,11 +145,12 @@ class TrackPresentationActionsSection extends HookConsumerWidget { { playlistNotifier.addTracksAtFirst(tracks); playlistNotifier.addCollection(options.collectionId); - if (options.collection is AlbumSimple) { - historyNotifier.addAlbums([options.collection as AlbumSimple]); + if (options.collection is SpotubeSimpleAlbumObject) { + historyNotifier.addAlbums( + [options.collection as SpotubeSimpleAlbumObject]); } else { - historyNotifier - .addPlaylists([options.collection as PlaylistSimple]); + historyNotifier.addPlaylists( + [options.collection as SpotubeSimplePlaylistObject]); } notifier.deselectAllTracks(); if (!context.mounted) return; @@ -158,11 +161,12 @@ class TrackPresentationActionsSection extends HookConsumerWidget { { playlistNotifier.addTracks(tracks); playlistNotifier.addCollection(options.collectionId); - if (options.collection is AlbumSimple) { - historyNotifier.addAlbums([options.collection as AlbumSimple]); + if (options.collection is SpotubeSimpleAlbumObject) { + historyNotifier.addAlbums( + [options.collection as SpotubeSimpleAlbumObject]); } else { - historyNotifier - .addPlaylists([options.collection as PlaylistSimple]); + historyNotifier.addPlaylists( + [options.collection as SpotubeSimplePlaylistObject]); } notifier.deselectAllTracks(); if (!context.mounted) return; diff --git a/lib/components/track_presentation/presentation_props.dart b/lib/components/track_presentation/presentation_props.dart index 144cf0e8..72f65c71 100644 --- a/lib/components/track_presentation/presentation_props.dart +++ b/lib/components/track_presentation/presentation_props.dart @@ -1,14 +1,14 @@ import 'dart:async'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class PaginationProps { final bool hasNextPage; final bool isLoading; final VoidCallback onFetchMore; final Future Function() onRefresh; - final Future> Function() onFetchAll; + final Future> Function() onFetchAll; const PaginationProps({ required this.hasNextPage, @@ -46,7 +46,7 @@ class TrackPresentationOptions { final String? ownerImage; final String image; final String routePath; - final List tracks; + final List tracks; final PaginationProps pagination; final bool isLiked; final String? shareUrl; @@ -67,11 +67,12 @@ class TrackPresentationOptions { this.shareUrl, this.isLiked = false, this.onHeart, - }) : assert(collection is AlbumSimple || collection is PlaylistSimple); + }) : assert(collection is SpotubeSimpleAlbumObject || + collection is SpotubeSimplePlaylistObject); - String get collectionId => collection is AlbumSimple - ? (collection as AlbumSimple).id! - : (collection as PlaylistSimple).id!; + String get collectionId => collection is SpotubeSimpleAlbumObject + ? (collection as SpotubeSimpleAlbumObject).id + : (collection as SpotubeSimplePlaylistObject).id; static TrackPresentationOptions of(BuildContext context) { return Data.of(context); diff --git a/lib/components/track_presentation/presentation_state.dart b/lib/components/track_presentation/presentation_state.dart index d3428861..32b7353a 100644 --- a/lib/components/track_presentation/presentation_state.dart +++ b/lib/components/track_presentation/presentation_state.dart @@ -1,14 +1,16 @@ import 'package:collection/collection.dart'; import 'package:fuzzywuzzy/fuzzywuzzy.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/utils/service_utils.dart'; class PresentationState { - final List selectedTracks; - final List presentationTracks; + final List selectedTracks; + final List presentationTracks; final SortBy sortBy; const PresentationState({ @@ -18,8 +20,8 @@ class PresentationState { }); PresentationState copyWith({ - List? selectedTracks, - List? presentationTracks, + List? selectedTracks, + List? presentationTracks, SortBy? sortBy, }) { return PresentationState( @@ -34,15 +36,15 @@ class PresentationStateNotifier extends AutoDisposeFamilyNotifier { @override PresentationState build(collection) { - if (arg case PlaylistSimple() || AlbumSimple()) { + if (arg case SpotubeSimplePlaylistObject() || SpotubeSimpleAlbumObject()) { if (isSavedTrackPlaylist) { ref.listen( - likedTracksProvider, + metadataPluginSavedTracksProvider, (previous, next) { next.whenData((value) { state = state.copyWith( presentationTracks: ServiceUtils.sortTracks( - value, + value.items, state.sortBy, ), ); @@ -51,9 +53,11 @@ class PresentationStateNotifier ); } else { ref.listen( - arg is PlaylistSimple - ? playlistTracksProvider((arg as PlaylistSimple).id!) - : albumTracksProvider((arg as AlbumSimple)), + arg is SpotubeSimplePlaylistObject + ? metadataPluginPlaylistTracksProvider( + (arg as SpotubeSimplePlaylistObject).id) + : metadataPluginAlbumTracksProvider( + (arg as SpotubeSimpleAlbumObject).id), (previous, next) { next.whenData((value) { state = state.copyWith( @@ -76,36 +80,39 @@ class PresentationStateNotifier } bool get isSavedTrackPlaylist => - arg is PlaylistSimple && - (arg as PlaylistSimple).id == "user-liked-tracks"; + arg is SpotubeSimplePlaylistObject && + (arg as SpotubeSimplePlaylistObject).id == "user-liked-tracks"; - List get tracks { + List get tracks { assert( - arg is PlaylistSimple || arg is AlbumSimple, - "arg must be PlaylistSimple or AlbumSimple", + arg is SpotubeSimplePlaylistObject || arg is SpotubeSimpleAlbumObject, + "arg must be SpotubeSimplePlaylistObject or SpotubeSimpleAlbumObject", ); - final isPlaylist = arg is PlaylistSimple; + final isPlaylist = arg is SpotubeSimplePlaylistObject; final tracks = switch ((isPlaylist, isSavedTrackPlaylist)) { - (true, true) => ref.read(likedTracksProvider).asData?.value, + (true, true) => + ref.read(metadataPluginSavedTracksProvider).asData?.value.items, (true, false) => ref - .read(playlistTracksProvider((arg as PlaylistSimple).id!)) + .read(metadataPluginPlaylistTracksProvider( + (arg as SpotubeSimplePlaylistObject).id)) .asData ?.value .items, _ => ref - .read(albumTracksProvider((arg as AlbumSimple))) + .read(metadataPluginAlbumTracksProvider( + (arg as SpotubeSimpleAlbumObject).id)) .asData ?.value .items, } ?? - []; + []; return tracks; } - void selectTrack(Track track) { + void selectTrack(SpotubeTrackObject track) { if (state.selectedTracks.any((e) => e.id == track.id)) { return; } @@ -121,7 +128,7 @@ class PresentationStateNotifier ); } - void deselectTrack(Track track) { + void deselectTrack(SpotubeTrackObject track) { state = state.copyWith( selectedTracks: state.selectedTracks.where((e) => e != track).toList(), ); @@ -141,7 +148,7 @@ class PresentationStateNotifier state = state.copyWith( presentationTracks: ServiceUtils.sortTracks( tracks - .map((e) => (weightedRatio(e.name!, query), e)) + .map((e) => (weightedRatio(e.name, query), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) .where((e) => e.$1 > 50) .map((e) => e.$2) diff --git a/lib/components/track_presentation/presentation_top.dart b/lib/components/track_presentation/presentation_top.dart index 5935fa13..ff7f5167 100644 --- a/lib/components/track_presentation/presentation_top.dart +++ b/lib/components/track_presentation/presentation_top.dart @@ -3,8 +3,6 @@ import 'package:flutter/services.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -14,7 +12,6 @@ import 'package:spotube/components/track_presentation/use_is_user_playlist.dart' import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; class TrackPresentationTopSection extends HookConsumerWidget { const TrackPresentationTopSection({super.key}); @@ -26,25 +23,10 @@ class TrackPresentationTopSection extends HookConsumerWidget { final scale = context.theme.scaling; final isUserPlaylist = useIsUserPlaylist(ref, options.collectionId); - final playlistImage = (options.collection is PlaylistSimple && - (options.collection as PlaylistSimple).owner?.displayName == - "Spotify" && - Env.disableSpotifyImages) - ? ref.watch(playlistImageProvider(options.collectionId)) - : null; - final decorationImage = playlistImage != null - ? DecorationImage( - image: AssetImage(playlistImage.src), - fit: BoxFit.cover, - colorFilter: ColorFilter.mode( - playlistImage.color, - playlistImage.colorBlendMode, - ), - ) - : DecorationImage( - image: UniversalImage.imageProvider(options.image), - fit: BoxFit.cover, - ); + final decorationImage = DecorationImage( + image: UniversalImage.imageProvider(options.image), + fit: BoxFit.cover, + ); final imageDimension = mediaQuery.mdAndUp ? 200 : 120; @@ -116,7 +98,7 @@ class TrackPresentationTopSection extends HookConsumerWidget { builder: (context) { return PlaylistCreateDialog( playlistId: options.collectionId, - trackIds: options.tracks.map((e) => e.id!).toList(), + trackIds: options.tracks.map((e) => e.id).toList(), ); }, ); diff --git a/lib/components/track_presentation/use_action_callbacks.dart b/lib/components/track_presentation/use_action_callbacks.dart index 0012594a..ff49f4a4 100644 --- a/lib/components/track_presentation/use_action_callbacks.dart +++ b/lib/components/track_presentation/use_action_callbacks.dart @@ -2,11 +2,11 @@ import 'dart:math'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; @@ -45,14 +45,14 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) { final allTracks = await options.pagination.onFetchAll(); final remotePlayback = ref.read(connectProvider.notifier); await remotePlayback.load( - options.collection is AlbumSimple + options.collection is SpotubeSimpleAlbumObject ? WebSocketLoadEventData.album( tracks: allTracks, - collection: options.collection as AlbumSimple, + collection: options.collection as SpotubeSimpleAlbumObject, initialIndex: Random().nextInt(allTracks.length)) : WebSocketLoadEventData.playlist( tracks: allTracks, - collection: options.collection as PlaylistSimple, + collection: options.collection as SpotubeSimplePlaylistObject, initialIndex: Random().nextInt(allTracks.length), ), ); @@ -65,10 +65,12 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) { ); await audioPlayer.setShuffle(true); playlistNotifier.addCollection(options.collectionId); - if (options.collection is AlbumSimple) { - historyNotifier.addAlbums([options.collection as AlbumSimple]); + if (options.collection is SpotubeSimpleAlbumObject) { + historyNotifier + .addAlbums([options.collection as SpotubeSimpleAlbumObject]); } else { - historyNotifier.addPlaylists([options.collection as PlaylistSimple]); + historyNotifier.addPlaylists( + [options.collection as SpotubeSimplePlaylistObject]); } final allTracks = await options.pagination.onFetchAll(); @@ -96,23 +98,25 @@ UseActionCallbacks useActionCallbacks(WidgetRef ref) { final allTracks = await options.pagination.onFetchAll(); final remotePlayback = ref.read(connectProvider.notifier); await remotePlayback.load( - options.collection is AlbumSimple + options.collection is SpotubeSimpleAlbumObject ? WebSocketLoadEventData.album( tracks: allTracks, - collection: options.collection as AlbumSimple, + collection: options.collection as SpotubeSimpleAlbumObject, ) : WebSocketLoadEventData.playlist( tracks: allTracks, - collection: options.collection as PlaylistSimple, + collection: options.collection as SpotubeSimplePlaylistObject, ), ); } else { await playlistNotifier.load(initialTracks, autoPlay: true); playlistNotifier.addCollection(options.collectionId); - if (options.collection is AlbumSimple) { - historyNotifier.addAlbums([options.collection as AlbumSimple]); + if (options.collection is SpotubeSimpleAlbumObject) { + historyNotifier + .addAlbums([options.collection as SpotubeSimpleAlbumObject]); } else { - historyNotifier.addPlaylists([options.collection as PlaylistSimple]); + historyNotifier.addPlaylists( + [options.collection as SpotubeSimplePlaylistObject]); } final allTracks = await options.pagination.onFetchAll(); diff --git a/lib/components/track_presentation/use_is_user_playlist.dart b/lib/components/track_presentation/use_is_user_playlist.dart index 2f87ccc8..18426118 100644 --- a/lib/components/track_presentation/use_is_user_playlist.dart +++ b/lib/components/track_presentation/use_is_user_playlist.dart @@ -1,17 +1,18 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; bool useIsUserPlaylist(WidgetRef ref, String playlistId) { - final userPlaylistsQuery = ref.watch(favoritePlaylistsProvider); - final me = ref.watch(meProvider); + final userPlaylistsQuery = ref.watch(metadataPluginSavedPlaylistsProvider); + final me = ref.watch(metadataPluginUserProvider); return useMemoized( () => userPlaylistsQuery.asData?.value.items.any((e) => e.id == playlistId && me.asData?.value != null && - e.owner?.id == me.asData?.value.id) ?? + e.owner.id == me.asData?.value?.id) ?? false, [userPlaylistsQuery.asData?.value, playlistId, me.asData?.value], ); diff --git a/lib/components/track_presentation/use_track_tile_play_callback.dart b/lib/components/track_presentation/use_track_tile_play_callback.dart index b519f781..99f44f1e 100644 --- a/lib/components/track_presentation/use_track_tile_play_callback.dart +++ b/lib/components/track_presentation/use_track_tile_play_callback.dart @@ -1,18 +1,19 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/presentation_state.dart'; import 'package:spotube/extensions/list.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; -Future Function(Track track, int index) useTrackTilePlayCallback( +Future Function(SpotubeTrackObject track, int index) + useTrackTilePlayCallback( WidgetRef ref, ) { final context = useContext(); @@ -26,7 +27,8 @@ Future Function(Track track, int index) useTrackTilePlayCallback( [playlist.collections, options.collectionId], ); - final onTapTrackTile = useCallback((Track track, int index) async { + final onTapTrackTile = + useCallback((SpotubeTrackObject track, int index) async { final state = ref.read(presentationStateProvider(options.collection)); final notifier = ref.read(presentationStateProvider(options.collection).notifier); @@ -52,15 +54,15 @@ Future Function(Track track, int index) useTrackTilePlayCallback( } else { final tracks = await options.pagination.onFetchAll(); await remotePlayback.load( - options.collection is AlbumSimple + options.collection is SpotubeSimpleAlbumObject ? WebSocketLoadEventData.album( tracks: tracks, - collection: options.collection as AlbumSimple, + collection: options.collection as SpotubeSimpleAlbumObject, initialIndex: index, ) : WebSocketLoadEventData.playlist( tracks: tracks, - collection: options.collection as PlaylistSimple, + collection: options.collection as SpotubeSimplePlaylistObject, initialIndex: index, ), ); @@ -76,10 +78,12 @@ Future Function(Track track, int index) useTrackTilePlayCallback( autoPlay: true, ); playlistNotifier.addCollection(options.collectionId); - if (options.collection is AlbumSimple) { - historyNotifier.addAlbums([options.collection as AlbumSimple]); + if (options.collection is SpotubeSimpleAlbumObject) { + historyNotifier + .addAlbums([options.collection as SpotubeSimpleAlbumObject]); } else { - historyNotifier.addPlaylists([options.collection as PlaylistSimple]); + historyNotifier.addPlaylists( + [options.collection as SpotubeSimplePlaylistObject]); } } } diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 0136c419..d9e31d78 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -8,7 +8,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotify/spotify.dart' hide Offset; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -21,15 +20,18 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/local_track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/services/metadata/endpoints/error.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -50,8 +52,9 @@ enum TrackOptionValue { startRadio, } +/// [track] must be a [SpotubeFullTrackObject] or [SpotubeLocalTrackObject] class TrackOptions extends HookConsumerWidget { - final Track track; + final SpotubeTrackObject track; final bool userPlaylist; final String? playlistId; final ObjectRef?>? showMenuCbRef; @@ -63,9 +66,12 @@ class TrackOptions extends HookConsumerWidget { this.userPlaylist = false, this.playlistId, this.icon, - }); + }) : assert( + track is SpotubeFullTrackObject || track is SpotubeLocalTrackObject, + "Track must be a SpotubeFullTrackObject, SpotubeLocalTrackObject", + ); - void actionShare(BuildContext context, Track track) { + void actionShare(BuildContext context, SpotubeTrackObject track) { final data = "https://open.spotify.com/track/${track.id}"; Clipboard.setData(ClipboardData(text: data)).then((_) { if (context.mounted) { @@ -87,7 +93,7 @@ class TrackOptions extends HookConsumerWidget { void actionAddToPlaylist( BuildContext context, - Track track, + SpotubeTrackObject track, ) { /// showDialog doesn't work for some reason. So we have to /// manually push a Dialog Route in the Navigator to get it working @@ -105,32 +111,32 @@ class TrackOptions extends HookConsumerWidget { void actionStartRadio( BuildContext context, WidgetRef ref, - Track track, + SpotubeTrackObject track, ) async { final playback = ref.read(audioPlayerProvider.notifier); final playlist = ref.read(audioPlayerProvider); - final spotify = ref.read(spotifyProvider); final query = "${track.name} Radio"; - final pages = await spotify.invoke( - (api) => api.search.get(query, types: [SearchType.playlist]).first(), - ); + final metadataPlugin = await ref.read(metadataPluginProvider.future); - final radios = pages - .expand((e) => e.items?.cast().toList() ?? []) - .toList(); + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No default metadata plugin set", + ); + } - final artists = track.artists!.map((e) => e.name); + final pages = await metadataPlugin.search.playlists(query); - final radio = radios.firstWhere( + final artists = track.artists.map((e) => e.name); + + final radio = pages.items.firstWhere( (e) { - final validPlaylists = - artists.where((a) => e.description!.contains(a!)); + final validPlaylists = artists.where((a) => e.description.contains(a)); return e.name == "${track.name} Radio" && (validPlaylists.length >= 2 || validPlaylists.length == artists.length) && - e.owner?.displayName == "Spotify"; + e.owner.name == "Spotify"; }, - orElse: () => radios.first, + orElse: () => pages.items.first, ); bool replaceQueue = false; @@ -154,10 +160,10 @@ class TrackOptions extends HookConsumerWidget { } else { await playback.addTrack(track); } - - final tracks = await spotify.invoke( - (api) => api.playlists.getTracksByPlaylistId(radio.id!).all(), - ); + await ref.read(metadataPluginPlaylistTracksProvider(radio.id).future); + final tracks = await ref + .read(metadataPluginPlaylistTracksProvider(radio.id).notifier) + .fetchAll(); await playback.addTracks( tracks.toList() @@ -179,7 +185,7 @@ class TrackOptions extends HookConsumerWidget { ref.watch(downloadManagerProvider); final downloadManager = ref.watch(downloadManagerProvider.notifier); final blacklist = ref.watch(blacklistProvider); - final me = ref.watch(meProvider); + final me = ref.watch(metadataPluginUserProvider); final favorites = useTrackToggleLike(track, ref); @@ -192,23 +198,32 @@ class TrackOptions extends HookConsumerWidget { final removingTrack = useState(null); final favoritePlaylistsNotifier = - ref.watch(favoritePlaylistsProvider.notifier); + ref.watch(metadataPluginSavedPlaylistsProvider.notifier); - final isInQueue = useMemoized(() { - if (playlist.activeTrack == null) return false; - return downloadManager.isActive(playlist.activeTrack!); + final isInDownloadQueue = useMemoized(() { + if (playlist.activeTrack == null || + playlist.activeTrack! is SpotubeLocalTrackObject) { + return false; + } + return downloadManager.isActive( + playlist.activeTrack! as SpotubeFullTrackObject, + ); }, [ playlist.activeTrack, downloadManager, ]); final progressNotifier = useMemoized(() { - final spotubeTrack = downloadManager.mapToSourcedTrack(track); - if (spotubeTrack == null) return null; - return downloadManager.getProgressNotifier(spotubeTrack); + if (track is! SpotubeFullTrackObject) { + return throw Exception( + "Invalid usage of `progressNotifierFuture`. Track must be a SpotubeFullTrackObject to get download progress", + ); + } + return downloadManager + .getProgressNotifier(track as SpotubeFullTrackObject); }); - final isLocalTrack = track is LocalTrack; + final isLocalTrack = track is SpotubeLocalTrackObject; final adaptivePopSheetList = AdaptivePopSheetList( tooltip: context.l10n.more_actions, @@ -220,7 +235,7 @@ class TrackOptions extends HookConsumerWidget { // ); break; case TrackOptionValue.delete: - await File((track as LocalTrack).path).delete(); + await File((track as SpotubeLocalTrackObject).path).delete(); ref.invalidate(localTracksProvider); break; case TrackOptionValue.addToQueue: @@ -232,7 +247,7 @@ class TrackOptions extends HookConsumerWidget { builder: (context, overlay) { return SurfaceCard( child: Text( - context.l10n.added_track_to_queue(track.name!), + context.l10n.added_track_to_queue(track.name), textAlign: TextAlign.center, ), ); @@ -250,7 +265,7 @@ class TrackOptions extends HookConsumerWidget { builder: (context, overlay) { return SurfaceCard( child: Text( - context.l10n.track_will_play_next(track.name!), + context.l10n.track_will_play_next(track.name), textAlign: TextAlign.center, ), ); @@ -259,7 +274,7 @@ class TrackOptions extends HookConsumerWidget { } break; case TrackOptionValue.removeFromQueue: - playback.removeTrack(track.id!); + playback.removeTrack(track.id); if (context.mounted) { showToast( @@ -269,7 +284,7 @@ class TrackOptions extends HookConsumerWidget { return SurfaceCard( child: Text( context.l10n.removed_track_from_queue( - track.name!, + track.name, ), textAlign: TextAlign.center, ), @@ -285,19 +300,19 @@ class TrackOptions extends HookConsumerWidget { actionAddToPlaylist(context, track); break; case TrackOptionValue.removeFromPlaylist: - removingTrack.value = track.uri; + removingTrack.value = track.externalUri; favoritePlaylistsNotifier - .removeTracks(playlistId ?? "", [track.id!]); + .removeTracks(playlistId ?? "", [track.id]); break; case TrackOptionValue.blacklist: if (isBlackListed == null) break; if (isBlackListed == true) { - await ref.read(blacklistProvider.notifier).remove(track.id!); + await ref.read(blacklistProvider.notifier).remove(track.id); } else { await ref.read(blacklistProvider.notifier).add( BlacklistTableCompanion.insert( - name: track.name!, - elementId: track.id!, + name: track.name, + elementId: track.id, elementType: BlacklistedType.track, ), ); @@ -311,16 +326,19 @@ class TrackOptions extends HookConsumerWidget { await launchUrlString(url); break; case TrackOptionValue.details: + if (track is! SpotubeFullTrackObject) break; showDialog( context: context, builder: (context) => ConstrainedBox( constraints: const BoxConstraints(maxWidth: 400), - child: TrackDetailsDialog(track: track), + child: + TrackDetailsDialog(track: track as SpotubeFullTrackObject), ), ); break; case TrackOptionValue.download: - await downloadManager.addToQueue(track); + if (track is! SpotubeFullTrackObject) break; + await downloadManager.addToQueue(track as SpotubeFullTrackObject); break; case TrackOptionValue.startRadio: actionStartRadio(context, ref, track); @@ -336,23 +354,23 @@ class TrackOptions extends HookConsumerWidget { child: ClipRRect( borderRadius: BorderRadius.circular(10), child: UniversalImage( - path: track.album!.images + path: track.album.images .asUrlString(placeholder: ImagePlaceholder.albumArt), fit: BoxFit.cover, ), ), ), title: Text( - track.name!, + track.name, maxLines: 1, overflow: TextOverflow.ellipsis, ).semiBold(), subtitle: Align( alignment: Alignment.centerLeft, child: ArtistLink( - artists: track.artists!, + artists: track.artists, onOverflowArtistClick: () => context.navigateTo( - TrackRoute(trackId: track.id!), + TrackRoute(trackId: track.id), ), ), ), @@ -375,7 +393,7 @@ class TrackOptions extends HookConsumerWidget { children: [ Text(context.l10n.go_to_album), Text( - track.album!.name!, + track.album.name, style: context.theme.typography.xSmall, ), ], @@ -435,12 +453,12 @@ class TrackOptions extends HookConsumerWidget { if (!isLocalTrack) AdaptiveMenuButton( value: TrackOptionValue.download, - enabled: !isInQueue, - leading: isInQueue + enabled: !isInDownloadQueue, + leading: isInDownloadQueue ? HookBuilder(builder: (context) { - final progress = useListenable(progressNotifier!); + final progress = useListenable(progressNotifier); return CircularProgressIndicator( - value: progress.value, + value: progress?.value, ); }) : const Icon(SpotubeIcons.download), diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index 9d9045c5..a3207353 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -7,7 +7,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/hover_builder.dart'; @@ -16,11 +15,9 @@ import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/duration.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/models/local_track.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/audio_player/state.dart'; import 'package:spotube/provider/blacklist_provider.dart'; @@ -29,7 +26,7 @@ import 'package:spotube/utils/platform.dart'; class TrackTile extends HookConsumerWidget { /// [index] will not be shown if null final int? index; - final Track track; + final SpotubeTrackObject track; final bool selected; final ValueChanged? onChanged; final Future Function()? onTap; @@ -151,7 +148,7 @@ class TrackTile extends HookConsumerWidget { image: DecorationImage( fit: BoxFit.cover, image: UniversalImage.imageProvider( - (track.album?.images).asUrlString( + (track.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, ), ), @@ -217,8 +214,8 @@ class TrackTile extends HookConsumerWidget { Expanded( flex: 6, child: switch (track) { - LocalTrack() => Text( - track.name!, + SpotubeLocalTrackObject() => Text( + track.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -233,10 +230,10 @@ class TrackTile extends HookConsumerWidget { ), onPressed: () { context - .navigateTo(TrackRoute(trackId: track.id!)); + .navigateTo(TrackRoute(trackId: track.id)); }, child: Text( - track.name!, + track.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -251,22 +248,22 @@ class TrackTile extends HookConsumerWidget { Expanded( flex: 4, child: switch (track) { - LocalTrack() => Text( - track.album!.name!, + SpotubeLocalTrackObject() => Text( + track.album.name, maxLines: 1, overflow: TextOverflow.ellipsis, ), _ => Align( alignment: Alignment.centerLeft, - /* child: LinkText( - track.album!.name!, + child: LinkText( + track.album.name, AlbumRoute( - album: track.album!, - id: track.album!.id!, + album: track.album, + id: track.album.id, ), push: true, overflow: TextOverflow.ellipsis, - ), */ + ), ) }, ), @@ -275,18 +272,18 @@ class TrackTile extends HookConsumerWidget { ), subtitle: Align( alignment: Alignment.centerLeft, - child: track is LocalTrack + child: track is SpotubeLocalTrackObject ? Text( - track.artists?.asString() ?? '', + track.artists.asString(), ) : ClipRect( child: ConstrainedBox( constraints: const BoxConstraints(maxHeight: 40), child: ArtistLink( - artists: track.artists ?? [], + artists: track.artists, onOverflowArtistClick: () { context.navigateTo( - TrackRoute(trackId: track.id!), + TrackRoute(trackId: track.id), ); }, ), @@ -298,7 +295,7 @@ class TrackTile extends HookConsumerWidget { children: [ const SizedBox(width: 8), Text( - Duration(milliseconds: track.durationMs ?? 0) + Duration(milliseconds: track.durationMs) .toHumanReadableString(padZero: false), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/extensions/album_simple.dart b/lib/extensions/album_simple.dart deleted file mode 100644 index 5678390c..00000000 --- a/lib/extensions/album_simple.dart +++ /dev/null @@ -1,21 +0,0 @@ -import 'package:spotify/spotify.dart'; - -extension AlbumExtensions on AlbumSimple { - Album toAlbum() { - Album album = Album(); - album.albumType = albumType; - album.artists = artists; - album.availableMarkets = availableMarkets; - album.externalUrls = externalUrls; - album.href = href; - album.id = id; - album.images = images; - album.name = name; - album.releaseDate = releaseDate; - album.releaseDatePrecision = releaseDatePrecision; - album.tracks = tracks; - album.type = type; - album.uri = uri; - return album; - } -} diff --git a/lib/extensions/artist_simple.dart b/lib/extensions/artist_simple.dart deleted file mode 100644 index 7997355d..00000000 --- a/lib/extensions/artist_simple.dart +++ /dev/null @@ -1,7 +0,0 @@ -import 'package:spotify/spotify.dart'; - -extension ArtistExtension on List { - String asString() { - return map((e) => e.name?.replaceAll(",", " ")).join(", "); - } -} diff --git a/lib/extensions/image.dart b/lib/extensions/image.dart deleted file mode 100644 index ee78653a..00000000 --- a/lib/extensions/image.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/utils/primitive_utils.dart'; -import 'package:collection/collection.dart'; - -enum ImagePlaceholder { - albumArt, - artist, - collection, - online, -} - -extension SpotifyImageExtensions on List? { - String asUrlString({ - int index = 1, - required ImagePlaceholder placeholder, - }) { - final String placeholderUrl = { - ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, - ImagePlaceholder.artist: Assets.userPlaceholder.path, - ImagePlaceholder.collection: Assets.placeholder.path, - ImagePlaceholder.online: - "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", - }[placeholder]!; - - final sortedImage = this?.sorted((a, b) => a.width!.compareTo(b.width!)); - - return sortedImage != null && sortedImage.isNotEmpty - ? sortedImage[ - index > sortedImage.length - 1 ? sortedImage.length - 1 : index] - .url! - : placeholderUrl; - } -} diff --git a/lib/extensions/track.dart b/lib/extensions/track.dart deleted file mode 100644 index bfe1f639..00000000 --- a/lib/extensions/track.dart +++ /dev/null @@ -1,107 +0,0 @@ -import 'dart:io'; -import 'dart:typed_data'; - -import 'package:metadata_god/metadata_god.dart'; -import 'package:path/path.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:spotube/services/logger/logger.dart'; - -extension TrackExtensions on Track { - Track fromFile( - File file, { - Metadata? metadata, - String? art, - }) { - album = Album() - ..name = metadata?.album ?? "Unknown" - ..images = [if (art != null) Image()..url = art] - ..genres = [if (metadata?.genre != null) metadata!.genre!] - ..artists = [ - Artist() - ..name = metadata?.albumArtist ?? "Unknown" - ..id = metadata?.albumArtist ?? "Unknown" - ..type = "artist", - ] - ..id = metadata?.album - ..releaseDate = metadata?.year?.toString(); - artists = [ - Artist() - ..name = metadata?.artist ?? "Unknown" - ..id = metadata?.artist ?? "Unknown" - ]; - - id = metadata?.title ?? basenameWithoutExtension(file.path); - name = metadata?.title ?? basenameWithoutExtension(file.path); - type = "track"; - uri = file.path; - durationMs = (metadata?.durationMs?.toInt() ?? 0); - - return this; - } - - Metadata toMetadata({ - required int fileLength, - Uint8List? imageBytes, - }) { - return Metadata( - title: name, - artist: artists?.map((a) => a.name).join(", "), - album: album?.name, - albumArtist: artists?.map((a) => a.name).join(", "), - year: album?.releaseDate != null - ? int.tryParse(album!.releaseDate!.split("-").first) ?? 1969 - : 1969, - trackNumber: trackNumber, - discNumber: discNumber, - durationMs: durationMs?.toDouble() ?? 0.0, - fileSize: BigInt.from(fileLength), - trackTotal: album?.tracks?.length ?? 0, - picture: imageBytes != null - ? Picture( - data: imageBytes, - // Spotify images are always JPEGs - mimeType: 'image/jpeg', - ) - : null, - ); - } -} - -extension IterableTrackSimpleExtensions on Iterable { - Future> asTracks(AlbumSimple album, ref) async { - try { - final spotify = ref.read(spotifyProvider); - final tracks = await spotify.invoke((api) => - api.tracks.list(map((trackSimple) => trackSimple.id!).toList())); - return tracks.toList(); - } catch (e, stack) { - // Ignore errors and create the track locally - AppLogger.reportError(e, stack); - - List tracks = []; - for (final trackSimple in this) { - Track track = Track(); - track.album = album; - track.name = trackSimple.name; - track.artists = trackSimple.artists; - track.availableMarkets = trackSimple.availableMarkets; - track.discNumber = trackSimple.discNumber; - track.durationMs = trackSimple.durationMs; - track.explicit = trackSimple.explicit; - track.externalUrls = trackSimple.externalUrls; - track.href = trackSimple.href; - track.id = trackSimple.id; - track.isPlayable = trackSimple.isPlayable; - track.linkedFrom = trackSimple.linkedFrom; - track.previewUrl = trackSimple.previewUrl; - track.trackNumber = trackSimple.trackNumber; - track.type = trackSimple.type; - track.uri = trackSimple.uri; - tracks.add(track); - } - return tracks; - } - } -} diff --git a/lib/hooks/configurators/use_deep_linking.dart b/lib/hooks/configurators/use_deep_linking.dart index fa23091e..aaa4111c 100644 --- a/lib/hooks/configurators/use_deep_linking.dart +++ b/lib/hooks/configurators/use_deep_linking.dart @@ -5,7 +5,6 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:flutter_sharing_intent/flutter_sharing_intent.dart'; import 'package:flutter_sharing_intent/model/sharing_file.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -14,93 +13,95 @@ import 'package:spotube/utils/platform.dart'; final appLinks = AppLinks(); final linkStream = appLinks.stringLinkStream.asBroadcastStream(); +@Deprecated( + "Deeplinking is deprecated. Later a custom API for metadata provider will be created.") void useDeepLinking(WidgetRef ref, AppRouter router) { - // single instance no worries - final spotify = ref.watch(spotifyProvider); + // // single instance no worries + // final spotify = ref.watch(spotifyProvider); - useEffect(() { - void uriListener(List files) async { - for (final file in files) { - if (file.type != SharedMediaType.URL) continue; - final url = Uri.parse(file.value!); - if (url.pathSegments.length != 2) continue; + // useEffect(() { + // void uriListener(List files) async { + // for (final file in files) { + // if (file.type != SharedMediaType.URL) continue; + // final url = Uri.parse(file.value!); + // if (url.pathSegments.length != 2) continue; - switch (url.pathSegments.first) { - case "album": - final album = await spotify.invoke((api) { - return api.albums.get(url.pathSegments.last); - }); - // router.navigate( - // AlbumRoute(id: album.id!, album: album), - // ); - break; - case "artist": - router.navigate(ArtistRoute(artistId: url.pathSegments.last)); - break; - case "playlist": - final playlist = await spotify.invoke((api) { - return api.playlists.get(url.pathSegments.last); - }); - // router - // .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); - break; - case "track": - router.navigate(TrackRoute(trackId: url.pathSegments.last)); - break; - default: - break; - } - } - } + // switch (url.pathSegments.first) { + // case "album": + // final album = await spotify.invoke((api) { + // return api.albums.get(url.pathSegments.last); + // }); + // // router.navigate( + // // AlbumRoute(id: album.id!, album: album), + // // ); + // break; + // case "artist": + // router.navigate(ArtistRoute(artistId: url.pathSegments.last)); + // break; + // case "playlist": + // final playlist = await spotify.invoke((api) { + // return api.playlists.get(url.pathSegments.last); + // }); + // // router + // // .navigate(PlaylistRoute(id: playlist.id!, playlist: playlist)); + // break; + // case "track": + // router.navigate(TrackRoute(trackId: url.pathSegments.last)); + // break; + // default: + // break; + // } + // } + // } - StreamSubscription? mediaStream; + // StreamSubscription? mediaStream; - if (kIsMobile) { - FlutterSharingIntent.instance.getInitialSharing().then(uriListener); + // if (kIsMobile) { + // FlutterSharingIntent.instance.getInitialSharing().then(uriListener); - mediaStream = - FlutterSharingIntent.instance.getMediaStream().listen(uriListener); - } + // mediaStream = + // FlutterSharingIntent.instance.getMediaStream().listen(uriListener); + // } - final subscription = linkStream.listen((uri) async { - try { - final startSegment = uri.split(":").take(2).join(":"); - final endSegment = uri.split(":").last; + // final subscription = linkStream.listen((uri) async { + // try { + // final startSegment = uri.split(":").take(2).join(":"); + // final endSegment = uri.split(":").last; - switch (startSegment) { - case "spotify:album": - final album = await spotify.invoke((api) { - return api.albums.get(endSegment); - }); - // await router.navigate( - // AlbumRoute(id: album.id!, album: album), - // ); - break; - case "spotify:artist": - await router.navigate(ArtistRoute(artistId: endSegment)); - break; - case "spotify:track": - await router.navigate(TrackRoute(trackId: endSegment)); - break; - case "spotify:playlist": - final playlist = await spotify.invoke((api) { - return api.playlists.get(endSegment); - }); - // await router.navigate( - // PlaylistRoute(id: playlist.id!, playlist: playlist), - // ); - break; - default: - break; - } - } catch (e, stack) { - AppLogger.reportError(e, stack); - } - }); + // switch (startSegment) { + // case "spotify:album": + // final album = await spotify.invoke((api) { + // return api.albums.get(endSegment); + // }); + // // await router.navigate( + // // AlbumRoute(id: album.id!, album: album), + // // ); + // break; + // case "spotify:artist": + // await router.navigate(ArtistRoute(artistId: endSegment)); + // break; + // case "spotify:track": + // await router.navigate(TrackRoute(trackId: endSegment)); + // break; + // case "spotify:playlist": + // final playlist = await spotify.invoke((api) { + // return api.playlists.get(endSegment); + // }); + // // await router.navigate( + // // PlaylistRoute(id: playlist.id!, playlist: playlist), + // // ); + // break; + // default: + // break; + // } + // } catch (e, stack) { + // AppLogger.reportError(e, stack); + // } + // }); - return () { - mediaStream?.cancel(); - subscription.cancel(); - }; - }, [spotify]); + // return () { + // mediaStream?.cancel(); + // subscription.cancel(); + // }; + // }, [spotify]); } diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index 96628442..81a66168 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -2,10 +2,8 @@ import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 8186fd92..51b5b880 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -10,9 +10,9 @@ import 'package:media_kit/media_kit.dart' hide Track; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' show ThemeMode, Colors; -import 'package:spotify/spotify.dart' hide Playlist; import 'package:spotube/models/database/database.steps.dart'; import 'package:spotube/models/lyrics.dart'; +import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/kv_store/encrypted_kv_store.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index beccaef8..ef277bc5 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -3,8 +3,8 @@ import 'package:drift/internal/versioned_schema.dart' as i0; import 'package:drift/drift.dart' as i1; import 'package:drift/drift.dart'; // ignore_for_file: type=lint,unused_import import 'package:flutter/material.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/services/sourced_track/enums.dart'; // GENERATED BY drift_dev, DO NOT MODIFY. diff --git a/lib/models/database/tables/history.dart b/lib/models/database/tables/history.dart index 23c16f17..2faeba9a 100644 --- a/lib/models/database/tables/history.dart +++ b/lib/models/database/tables/history.dart @@ -16,10 +16,12 @@ class HistoryTable extends Table { } extension HistoryItemParseExtension on HistoryTableData { - PlaylistSimple? get playlist => - type == HistoryEntryType.playlist ? PlaylistSimple.fromJson(data) : null; - AlbumSimple? get album => - type == HistoryEntryType.album ? AlbumSimple.fromJson(data) : null; - Track? get track => - type == HistoryEntryType.track ? Track.fromJson(data) : null; + SpotubeSimplePlaylistObject? get playlist => type == HistoryEntryType.playlist + ? SpotubeSimplePlaylistObject.fromJson(data) + : null; + SpotubeSimpleAlbumObject? get album => type == HistoryEntryType.album + ? SpotubeSimpleAlbumObject.fromJson(data) + : null; + SpotubeTrackObject? get track => + type == HistoryEntryType.track ? SpotubeTrackObject.fromJson(data) : null; } diff --git a/lib/models/local_track.dart b/lib/models/local_track.dart deleted file mode 100644 index def3b64f..00000000 --- a/lib/models/local_track.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:spotify/spotify.dart'; - -class LocalTrack extends Track { - final String path; - - LocalTrack.fromTrack({ - required Track track, - required this.path, - }) : super() { - album = track.album; - artists = track.artists; - availableMarkets = track.availableMarkets; - discNumber = track.discNumber; - durationMs = track.durationMs; - explicit = track.explicit; - externalIds = track.externalIds; - externalUrls = track.externalUrls; - href = track.href; - id = track.id; - isPlayable = track.isPlayable; - linkedFrom = track.linkedFrom; - name = track.name; - popularity = track.popularity; - previewUrl = track.previewUrl; - trackNumber = track.trackNumber; - type = track.type; - uri = track.uri; - } - - factory LocalTrack.fromJson(Map json) { - return LocalTrack.fromTrack( - track: Track.fromJson(json), - path: json['path'], - ); - } - - @override - Map toJson() { - return { - ...super.toJson(), - 'path': path, - }; - } -} diff --git a/lib/models/metadata/market.dart b/lib/models/metadata/market.dart new file mode 100644 index 00000000..caaef957 --- /dev/null +++ b/lib/models/metadata/market.dart @@ -0,0 +1,252 @@ +enum Market { + AD, + AE, + AF, + AG, + AI, + AL, + AM, + AO, + AQ, + AR, + AS, + AT, + AU, + AW, + AX, + AZ, + BA, + BB, + BD, + BE, + BF, + BG, + BH, + BI, + BJ, + BL, + BM, + BN, + BO, + BQ, + BR, + BS, + BT, + BV, + BW, + BY, + BZ, + CA, + CC, + CD, + CF, + CG, + CH, + CI, + CK, + CL, + CM, + CN, + CO, + CR, + CU, + CV, + CW, + CX, + CY, + CZ, + DE, + DJ, + DK, + DM, + DO, + DZ, + EC, + EE, + EG, + EH, + ER, + ES, + ET, + FI, + FJ, + FK, + FM, + FO, + FR, + GA, + GB, + GD, + GE, + GF, + GG, + GH, + GI, + GL, + GM, + GN, + GP, + GQ, + GR, + GS, + GT, + GU, + GW, + GY, + HK, + HM, + HN, + HR, + HT, + HU, + ID, + IE, + IL, + IM, + IN, + IO, + IQ, + IR, + IS, + IT, + JE, + JM, + JO, + JP, + KE, + KG, + KH, + KI, + KM, + KN, + KP, + KR, + KW, + KY, + KZ, + LA, + LB, + LC, + LI, + LK, + LR, + LS, + LT, + LU, + LV, + LY, + MA, + MC, + MD, + ME, + MF, + MG, + MH, + MK, + ML, + MM, + MN, + MO, + MP, + MQ, + MR, + MS, + MT, + MU, + MV, + MW, + MX, + MY, + MZ, + NA, + NC, + NE, + NF, + NG, + NI, + NL, + NO, + NP, + NR, + NU, + NZ, + OM, + PA, + PE, + PF, + PG, + PH, + PK, + PL, + PM, + PN, + PR, + PS, + PT, + PW, + PY, + QA, + RE, + RO, + RS, + RU, + RW, + SA, + SB, + SC, + SD, + SE, + SG, + SH, + SI, + SJ, + SK, + SL, + SM, + SN, + SO, + SR, + SS, + ST, + SV, + SX, + SY, + SZ, + TC, + TD, + TF, + TG, + TH, + TJ, + TK, + TL, + TM, + TN, + TO, + TR, + TT, + TV, + TW, + TZ, + UA, + UG, + UM, + US, + UY, + UZ, + VA, + VC, + VE, + VG, + VI, + VN, + VU, + WF, + WS, + XK, + YE, + YT, + ZA, + ZM, + ZW, +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index d7aebdb3..65a4d91f 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -1,11 +1,12 @@ library metadata_objects; +import 'dart:io'; import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:media_kit/media_kit.dart'; import 'package:metadata_god/metadata_god.dart'; +import 'package:path/path.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/utils/primitive_utils.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 45ce4e84..636a092f 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -2571,8 +2571,7 @@ mixin _$SpotubeSearchResponseObject { throw _privateConstructorUsedError; List get playlists => throw _privateConstructorUsedError; - List get tracks => - throw _privateConstructorUsedError; + List get tracks => throw _privateConstructorUsedError; /// Serializes this SpotubeSearchResponseObject to a JSON map. Map toJson() => throw _privateConstructorUsedError; @@ -2596,7 +2595,7 @@ abstract class $SpotubeSearchResponseObjectCopyWith<$Res> { {List albums, List artists, List playlists, - List tracks}); + List tracks}); } /// @nodoc @@ -2636,7 +2635,7 @@ class _$SpotubeSearchResponseObjectCopyWithImpl<$Res, tracks: null == tracks ? _value.tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, ) as $Val); } } @@ -2654,7 +2653,7 @@ abstract class _$$SpotubeSearchResponseObjectImplCopyWith<$Res> {List albums, List artists, List playlists, - List tracks}); + List tracks}); } /// @nodoc @@ -2693,7 +2692,7 @@ class __$$SpotubeSearchResponseObjectImplCopyWithImpl<$Res> tracks: null == tracks ? _value._tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, )); } } @@ -2706,7 +2705,7 @@ class _$SpotubeSearchResponseObjectImpl {required final List albums, required final List artists, required final List playlists, - required final List tracks}) + required final List tracks}) : _albums = albums, _artists = artists, _playlists = playlists, @@ -2740,9 +2739,9 @@ class _$SpotubeSearchResponseObjectImpl return EqualUnmodifiableListView(_playlists); } - final List _tracks; + final List _tracks; @override - List get tracks { + List get tracks { if (_tracks is EqualUnmodifiableListView) return _tracks; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tracks); @@ -2797,7 +2796,7 @@ abstract class _SpotubeSearchResponseObject {required final List albums, required final List artists, required final List playlists, - required final List tracks}) = + required final List tracks}) = _$SpotubeSearchResponseObjectImpl; factory _SpotubeSearchResponseObject.fromJson(Map json) = @@ -2810,7 +2809,7 @@ abstract class _SpotubeSearchResponseObject @override List get playlists; @override - List get tracks; + List get tracks; /// Create a copy of SpotubeSearchResponseObject /// with the given fields replaced by the non-null parameter values. @@ -2826,8 +2825,6 @@ SpotubeTrackObject _$SpotubeTrackObjectFromJson(Map json) { return SpotubeLocalTrackObject.fromJson(json); case 'full': return SpotubeFullTrackObject.fromJson(json); - case 'simple': - return SpotubeSimpleTrackObject.fromJson(json); default: throw CheckedFromJsonException(json, 'runtimeType', 'SpotubeTrackObject', @@ -2842,7 +2839,7 @@ mixin _$SpotubeTrackObject { String get externalUri => throw _privateConstructorUsedError; List get artists => throw _privateConstructorUsedError; - SpotubeSimpleAlbumObject? get album => throw _privateConstructorUsedError; + SpotubeSimpleAlbumObject get album => throw _privateConstructorUsedError; int get durationMs => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ @@ -2865,15 +2862,6 @@ mixin _$SpotubeTrackObject { String isrc, bool explicit) full, - required TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album) - simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -2897,15 +2885,6 @@ mixin _$SpotubeTrackObject { String isrc, bool explicit)? full, - TResult? Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs @@ -2929,15 +2908,6 @@ mixin _$SpotubeTrackObject { String isrc, bool explicit)? full, - TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -2945,21 +2915,18 @@ mixin _$SpotubeTrackObject { TResult map({ required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, - required TResult Function(SpotubeSimpleTrackObject value) simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? mapOrNull({ TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, - TResult? Function(SpotubeSimpleTrackObject value)? simple, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeMap({ TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, - TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), }) => throw _privateConstructorUsedError; @@ -2988,7 +2955,7 @@ abstract class $SpotubeTrackObjectCopyWith<$Res> { SpotubeSimpleAlbumObject album, int durationMs}); - $SpotubeSimpleAlbumObjectCopyWith<$Res>? get album; + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album; } /// @nodoc @@ -3031,7 +2998,7 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> : artists // ignore: cast_nullable_to_non_nullable as List, album: null == album - ? _value.album! + ? _value.album : album // ignore: cast_nullable_to_non_nullable as SpotubeSimpleAlbumObject, durationMs: null == durationMs @@ -3045,12 +3012,8 @@ class _$SpotubeTrackObjectCopyWithImpl<$Res, $Val extends SpotubeTrackObject> /// 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) { + $SpotubeSimpleAlbumObjectCopyWith<$Res> get album { + return $SpotubeSimpleAlbumObjectCopyWith<$Res>(_value.album, (value) { return _then(_value.copyWith(album: value) as $Val); }); } @@ -3132,16 +3095,6 @@ class __$$SpotubeLocalTrackObjectImplCopyWithImpl<$Res> as String, )); } - - /// 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 @@ -3244,15 +3197,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { String isrc, bool explicit) full, - required TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album) - simple, }) { return local(id, name, externalUri, artists, album, durationMs, path); } @@ -3279,15 +3223,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { String isrc, bool explicit)? full, - TResult? Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, }) { return local?.call(id, name, externalUri, artists, album, durationMs, path); } @@ -3314,15 +3249,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { String isrc, bool explicit)? full, - TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, required TResult orElse(), }) { if (local != null) { @@ -3336,7 +3262,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { TResult map({ required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, - required TResult Function(SpotubeSimpleTrackObject value) simple, }) { return local(this); } @@ -3346,7 +3271,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { TResult? mapOrNull({ TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, - TResult? Function(SpotubeSimpleTrackObject value)? simple, }) { return local?.call(this); } @@ -3356,7 +3280,6 @@ class _$SpotubeLocalTrackObjectImpl implements SpotubeLocalTrackObject { TResult maybeMap({ TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, - TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), }) { if (local != null) { @@ -3489,16 +3412,6 @@ 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 @@ -3614,15 +3527,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { String isrc, bool explicit) full, - required TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album) - simple, }) { return full( id, name, externalUri, artists, album, durationMs, isrc, explicit); @@ -3650,15 +3554,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { String isrc, bool explicit)? full, - TResult? Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, }) { return full?.call( id, name, externalUri, artists, album, durationMs, isrc, explicit); @@ -3686,15 +3581,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { String isrc, bool explicit)? full, - TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, required TResult orElse(), }) { if (full != null) { @@ -3709,7 +3595,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { TResult map({ required TResult Function(SpotubeLocalTrackObject value) local, required TResult Function(SpotubeFullTrackObject value) full, - required TResult Function(SpotubeSimpleTrackObject value) simple, }) { return full(this); } @@ -3719,7 +3604,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { TResult? mapOrNull({ TResult? Function(SpotubeLocalTrackObject value)? local, TResult? Function(SpotubeFullTrackObject value)? full, - TResult? Function(SpotubeSimpleTrackObject value)? simple, }) { return full?.call(this); } @@ -3729,7 +3613,6 @@ class _$SpotubeFullTrackObjectImpl implements SpotubeFullTrackObject { TResult maybeMap({ TResult Function(SpotubeLocalTrackObject value)? local, TResult Function(SpotubeFullTrackObject value)? full, - TResult Function(SpotubeSimpleTrackObject value)? simple, required TResult orElse(), }) { if (full != null) { @@ -3783,358 +3666,6 @@ abstract class SpotubeFullTrackObject implements SpotubeTrackObject { get copyWith => throw _privateConstructorUsedError; } -/// @nodoc -abstract class _$$SpotubeSimpleTrackObjectImplCopyWith<$Res> - implements $SpotubeTrackObjectCopyWith<$Res> { - factory _$$SpotubeSimpleTrackObjectImplCopyWith( - _$SpotubeSimpleTrackObjectImpl value, - $Res Function(_$SpotubeSimpleTrackObjectImpl) then) = - __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album}); - - @override - $SpotubeSimpleAlbumObjectCopyWith<$Res>? get album; -} - -/// @nodoc -class __$$SpotubeSimpleTrackObjectImplCopyWithImpl<$Res> - extends _$SpotubeTrackObjectCopyWithImpl<$Res, - _$SpotubeSimpleTrackObjectImpl> - implements _$$SpotubeSimpleTrackObjectImplCopyWith<$Res> { - __$$SpotubeSimpleTrackObjectImplCopyWithImpl( - _$SpotubeSimpleTrackObjectImpl _value, - $Res Function(_$SpotubeSimpleTrackObjectImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotubeTrackObject - /// 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(_$SpotubeSimpleTrackObjectImpl( - 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, - album: freezed == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as SpotubeSimpleAlbumObject?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotubeSimpleTrackObjectImpl implements SpotubeSimpleTrackObject { - _$SpotubeSimpleTrackObjectImpl( - {required this.id, - required this.name, - required this.externalUri, - required this.durationMs, - required this.explicit, - final List artists = const [], - this.album, - final String? $type}) - : _artists = artists, - $type = $type ?? 'simple'; - - factory _$SpotubeSimpleTrackObjectImpl.fromJson(Map json) => - _$$SpotubeSimpleTrackObjectImplFromJson(json); - - @override - final String id; - @override - final String name; - @override - final String externalUri; - @override - final int durationMs; - @override - final bool explicit; - final List _artists; - @override - @JsonKey() - List get artists { - if (_artists is EqualUnmodifiableListView) return _artists; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_artists); - } - - @override - final SpotubeSimpleAlbumObject? album; - - @JsonKey(name: 'runtimeType') - final String $type; - - @override - String toString() { - return 'SpotubeTrackObject.simple(id: $id, name: $name, externalUri: $externalUri, durationMs: $durationMs, explicit: $explicit, artists: $artists, album: $album)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotubeSimpleTrackObjectImpl && - (identical(other.id, id) || other.id == id) && - (identical(other.name, name) || other.name == name) && - (identical(other.externalUri, externalUri) || - other.externalUri == externalUri) && - (identical(other.durationMs, durationMs) || - other.durationMs == durationMs) && - (identical(other.explicit, explicit) || - other.explicit == explicit) && - const DeepCollectionEquality().equals(other._artists, _artists) && - (identical(other.album, album) || other.album == album)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - id, - name, - externalUri, - durationMs, - explicit, - const DeepCollectionEquality().hash(_artists), - album); - - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotubeSimpleTrackObjectImplCopyWith<_$SpotubeSimpleTrackObjectImpl> - get copyWith => __$$SpotubeSimpleTrackObjectImplCopyWithImpl< - _$SpotubeSimpleTrackObjectImpl>(this, _$identity); - - @override - @optionalTypeArgs - TResult when({ - required TResult Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String path) - local, - required TResult Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String isrc, - bool explicit) - full, - required TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album) - simple, - }) { - return simple(id, name, externalUri, durationMs, explicit, artists, album); - } - - @override - @optionalTypeArgs - TResult? whenOrNull({ - TResult? Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String path)? - local, - TResult? Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String isrc, - bool explicit)? - full, - TResult? Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List artists, - SpotubeSimpleAlbumObject? album)? - simple, - }) { - return simple?.call( - id, name, externalUri, durationMs, explicit, artists, album); - } - - @override - @optionalTypeArgs - TResult maybeWhen({ - TResult Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String path)? - local, - TResult Function( - String id, - String name, - String externalUri, - List artists, - SpotubeSimpleAlbumObject album, - int durationMs, - String isrc, - bool explicit)? - full, - TResult Function( - String id, - String name, - String externalUri, - int durationMs, - bool explicit, - List 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({ - required TResult Function(SpotubeLocalTrackObject value) local, - required TResult Function(SpotubeFullTrackObject value) full, - required TResult Function(SpotubeSimpleTrackObject value) simple, - }) { - return simple(this); - } - - @override - @optionalTypeArgs - TResult? mapOrNull({ - TResult? Function(SpotubeLocalTrackObject value)? local, - TResult? Function(SpotubeFullTrackObject value)? full, - TResult? Function(SpotubeSimpleTrackObject value)? simple, - }) { - return simple?.call(this); - } - - @override - @optionalTypeArgs - TResult maybeMap({ - TResult Function(SpotubeLocalTrackObject value)? local, - TResult Function(SpotubeFullTrackObject value)? full, - TResult Function(SpotubeSimpleTrackObject value)? simple, - required TResult orElse(), - }) { - if (simple != null) { - return simple(this); - } - return orElse(); - } - - @override - Map toJson() { - return _$$SpotubeSimpleTrackObjectImplToJson( - this, - ); - } -} - -abstract class SpotubeSimpleTrackObject implements SpotubeTrackObject { - factory SpotubeSimpleTrackObject( - {required final String id, - required final String name, - required final String externalUri, - required final int durationMs, - required final bool explicit, - final List artists, - final SpotubeSimpleAlbumObject? album}) = _$SpotubeSimpleTrackObjectImpl; - - factory SpotubeSimpleTrackObject.fromJson(Map json) = - _$SpotubeSimpleTrackObjectImpl.fromJson; - - @override - String get id; - @override - String get name; - @override - String get externalUri; - @override - int get durationMs; - bool get explicit; - @override - List get artists; - @override - SpotubeSimpleAlbumObject? get album; - - /// Create a copy of SpotubeTrackObject - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotubeSimpleTrackObjectImplCopyWith<_$SpotubeSimpleTrackObjectImpl> - get copyWith => throw _privateConstructorUsedError; -} - SpotubeUserObject _$SpotubeUserObjectFromJson(Map json) { return _SpotubeUserObject.fromJson(json); } diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 3303e324..75e2ec18 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -273,7 +273,7 @@ _$SpotubeSearchResponseObjectImpl _$$SpotubeSearchResponseObjectImplFromJson( Map.from(e as Map))) .toList(), tracks: (json['tracks'] as List) - .map((e) => SpotubeSimpleTrackObject.fromJson( + .map((e) => SpotubeFullTrackObject.fromJson( Map.from(e as Map))) .toList(), ); @@ -350,39 +350,6 @@ Map _$$SpotubeFullTrackObjectImplToJson( 'runtimeType': instance.$type, }; -_$SpotubeSimpleTrackObjectImpl _$$SpotubeSimpleTrackObjectImplFromJson( - Map json) => - _$SpotubeSimpleTrackObjectImpl( - id: json['id'] as String, - name: json['name'] as String, - externalUri: json['externalUri'] as String, - durationMs: (json['durationMs'] as num).toInt(), - explicit: json['explicit'] as bool, - artists: (json['artists'] as List?) - ?.map((e) => SpotubeSimpleArtistObject.fromJson( - Map.from(e as Map))) - .toList() ?? - const [], - album: json['album'] == null - ? null - : SpotubeSimpleAlbumObject.fromJson( - Map.from(json['album'] as Map)), - $type: json['runtimeType'] as String?, - ); - -Map _$$SpotubeSimpleTrackObjectImplToJson( - _$SpotubeSimpleTrackObjectImpl instance) => - { - 'id': instance.id, - 'name': instance.name, - 'externalUri': instance.externalUri, - 'durationMs': instance.durationMs, - 'explicit': instance.explicit, - 'artists': instance.artists.map((e) => e.toJson()).toList(), - 'album': instance.album?.toJson(), - 'runtimeType': instance.$type, - }; - _$SpotubeUserObjectImpl _$$SpotubeUserObjectImplFromJson(Map json) => _$SpotubeUserObjectImpl( id: json['id'] as String, diff --git a/lib/models/metadata/search.dart b/lib/models/metadata/search.dart index 4918c898..b39f063a 100644 --- a/lib/models/metadata/search.dart +++ b/lib/models/metadata/search.dart @@ -6,7 +6,7 @@ class SpotubeSearchResponseObject with _$SpotubeSearchResponseObject { required List albums, required List artists, required List playlists, - required List tracks, + required List tracks, }) = _SpotubeSearchResponseObject; factory SpotubeSearchResponseObject.fromJson(Map json) => diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index b7cf1a3e..9520daab 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -23,23 +23,54 @@ class SpotubeTrackObject with _$SpotubeTrackObject { required bool explicit, }) = SpotubeFullTrackObject; - factory SpotubeTrackObject.simple({ - required String id, - required String name, - required String externalUri, - required int durationMs, - required bool explicit, - @Default([]) List artists, - SpotubeSimpleAlbumObject? album, - }) = SpotubeSimpleTrackObject; + factory SpotubeTrackObject.localTrackFromFile( + File file, { + Metadata? metadata, + String? art, + }) { + return SpotubeLocalTrackObject( + id: file.absolute.path, + name: metadata?.title ?? basenameWithoutExtension(file.path), + externalUri: "file://${file.absolute.path}", + artists: metadata?.artist?.split(",").map((a) { + return SpotubeSimpleArtistObject( + id: a.trim(), + name: a.trim(), + externalUri: "file://${file.absolute.path}", + ); + }).toList() ?? + [ + SpotubeSimpleArtistObject( + id: "unknown", + name: "Unknown Artist", + externalUri: "file://${file.absolute.path}", + ), + ], + album: SpotubeSimpleAlbumObject( + albumType: SpotubeAlbumType.album, + id: metadata?.album ?? "unknown", + name: metadata?.album ?? "Unknown Album", + externalUri: "file://${file.absolute.path}", + artists: [ + SpotubeSimpleArtistObject( + id: metadata?.albumArtist ?? "unknown", + name: metadata?.albumArtist ?? "Unknown Artist", + externalUri: "file://${file.absolute.path}", + ), + ], + releaseDate: + metadata?.year != null ? "${metadata!.year}-01-01" : "1970-01-01", + ), + durationMs: metadata?.durationMs?.toInt() ?? 0, + path: file.path, + ); + } factory SpotubeTrackObject.fromJson(Map json) => _$SpotubeTrackObjectFromJson( - json.containsKey("isrc") - ? {...json, "runtimeType": "full"} - : json.containsKey("path") - ? {...json, "runtimeType": "local"} - : {...json, "runtimeType": "simple"}, + json.containsKey("path") + ? {...json, "runtimeType": "local"} + : {...json, "runtimeType": "full"}, ); } diff --git a/lib/models/spotify/home_feed.dart b/lib/models/spotify/home_feed.dart deleted file mode 100644 index ad764304..00000000 --- a/lib/models/spotify/home_feed.dart +++ /dev/null @@ -1,247 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; -import 'package:spotify/spotify.dart'; - -part 'home_feed.freezed.dart'; -part 'home_feed.g.dart'; - -@freezed -class SpotifySectionPlaylist with _$SpotifySectionPlaylist { - const SpotifySectionPlaylist._(); - - const factory SpotifySectionPlaylist({ - required String description, - required String format, - required List images, - required String name, - required String owner, - required String uri, - }) = _SpotifySectionPlaylist; - - factory SpotifySectionPlaylist.fromJson(Map json) => - _$SpotifySectionPlaylistFromJson(json); - - String get id => uri.split(":").last; - - Playlist get asPlaylist { - return Playlist() - ..id = id - ..name = name - ..description = description - ..collaborative = false - ..images = images.map((e) => e.asImage).toList() - ..owner = (User()..displayName = owner) - ..uri = uri - ..type = "playlist"; - } -} - -@freezed -class SpotifySectionArtist with _$SpotifySectionArtist { - const SpotifySectionArtist._(); - - const factory SpotifySectionArtist({ - required String name, - required String uri, - required List images, - }) = _SpotifySectionArtist; - - factory SpotifySectionArtist.fromJson(Map json) => - _$SpotifySectionArtistFromJson(json); - - String get id => uri.split(":").last; - - Artist get asArtist { - return Artist() - ..id = id - ..name = name - ..images = images.map((e) => e.asImage).toList() - ..type = "artist" - ..uri = uri; - } -} - -@freezed -class SpotifySectionAlbum with _$SpotifySectionAlbum { - const SpotifySectionAlbum._(); - - const factory SpotifySectionAlbum({ - required List artists, - required List images, - required String name, - required String uri, - }) = _SpotifySectionAlbum; - - factory SpotifySectionAlbum.fromJson(Map json) => - _$SpotifySectionAlbumFromJson(json); - - String get id => uri.split(":").last; - - Album get asAlbum { - return Album() - ..id = id - ..name = name - ..artists = artists.map((a) => a.asArtist).toList() - ..albumType = AlbumType.album - ..images = images.map((e) => e.asImage).toList() - ..uri = uri; - } -} - -@freezed -class SpotifySectionAlbumArtist with _$SpotifySectionAlbumArtist { - const SpotifySectionAlbumArtist._(); - - const factory SpotifySectionAlbumArtist({ - required String name, - required String uri, - }) = _SpotifySectionAlbumArtist; - - factory SpotifySectionAlbumArtist.fromJson(Map json) => - _$SpotifySectionAlbumArtistFromJson(json); - - String get id => uri.split(":").last; - - Artist get asArtist { - return Artist() - ..id = id - ..name = name - ..type = "artist" - ..uri = uri; - } -} - -@freezed -class SpotifySectionItemImage with _$SpotifySectionItemImage { - const SpotifySectionItemImage._(); - - const factory SpotifySectionItemImage({ - required num? height, - required String url, - required num? width, - }) = _SpotifySectionItemImage; - - factory SpotifySectionItemImage.fromJson(Map json) => - _$SpotifySectionItemImageFromJson(json); - - Image get asImage { - return Image() - ..height = height?.toInt() - ..width = width?.toInt() - ..url = url; - } -} - -@freezed -class SpotifyHomeFeedSectionItem with _$SpotifyHomeFeedSectionItem { - factory SpotifyHomeFeedSectionItem({ - required String typename, - SpotifySectionPlaylist? playlist, - SpotifySectionArtist? artist, - SpotifySectionAlbum? album, - }) = _SpotifyHomeFeedSectionItem; - - factory SpotifyHomeFeedSectionItem.fromJson(Map json) => - _$SpotifyHomeFeedSectionItemFromJson(json); -} - -@freezed -class SpotifyHomeFeedSection with _$SpotifyHomeFeedSection { - factory SpotifyHomeFeedSection({ - required String typename, - String? title, - required String uri, - required List items, - }) = _SpotifyHomeFeedSection; - - factory SpotifyHomeFeedSection.fromJson(Map json) => - _$SpotifyHomeFeedSectionFromJson(json); -} - -@freezed -class SpotifyHomeFeed with _$SpotifyHomeFeed { - factory SpotifyHomeFeed({ - required String greeting, - required List sections, - }) = _SpotifyHomeFeed; - - factory SpotifyHomeFeed.fromJson(Map json) => - _$SpotifyHomeFeedFromJson(json); -} - -Map transformSectionItemTypeJsonMap( - Map json) { - final data = json["content"]["data"]; - final objType = json["content"]["data"]["__typename"]; - return { - "typename": json["content"]["__typename"], - if (objType == "Playlist") - "playlist": { - "name": data["name"], - "description": data["description"], - "format": data["format"], - "images": (data["images"]["items"] as List) - .expand((j) => j["sources"] as dynamic) - .toList() - .cast>(), - "owner": data["ownerV2"]["data"]["name"], - "uri": data["uri"] - }, - if (objType == "Artist") - "artist": { - "name": data["profile"]["name"], - "uri": data["uri"], - "images": data["visuals"]["avatarImage"]["sources"], - }, - if (objType == "Album") - "album": { - "name": data["name"], - "uri": data["uri"], - "images": data["coverArt"]["sources"], - "artists": data["artists"]["items"] - .map( - (artist) => { - "name": artist["profile"]["name"], - "uri": artist["uri"], - }, - ) - .toList() - }, - }; -} - -Map transformSectionItemJsonMap(Map json) { - return { - "typename": json["data"]["__typename"], - "title": json["data"]?["title"]?["text"], - "uri": json["uri"], - "items": (json["sectionItems"]["items"] as List) - .map( - (data) => - transformSectionItemTypeJsonMap(data as Map) - as dynamic, - ) - .where( - (w) => - w["playlist"] != null || - w["artist"] != null || - w["album"] != null, - ) - .toList() - .cast>() - }; -} - -Map transformHomeFeedJsonMap(Map json) { - return { - "greeting": json["data"]["home"]["greeting"]["text"], - "sections": - (json["data"]["home"]["sectionContainer"]["sections"]["items"] as List) - .map( - (item) => - transformSectionItemJsonMap(item as Map) - as dynamic, - ) - .toList() - .cast>() - }; -} diff --git a/lib/models/spotify/home_feed.freezed.dart b/lib/models/spotify/home_feed.freezed.dart deleted file mode 100644 index 5076da29..00000000 --- a/lib/models/spotify/home_feed.freezed.dart +++ /dev/null @@ -1,1776 +0,0 @@ -// 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 'home_feed.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(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'); - -SpotifySectionPlaylist _$SpotifySectionPlaylistFromJson( - Map json) { - return _SpotifySectionPlaylist.fromJson(json); -} - -/// @nodoc -mixin _$SpotifySectionPlaylist { - String get description => throw _privateConstructorUsedError; - String get format => throw _privateConstructorUsedError; - List get images => - throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get owner => throw _privateConstructorUsedError; - String get uri => throw _privateConstructorUsedError; - - /// Serializes this SpotifySectionPlaylist to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifySectionPlaylist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifySectionPlaylistCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifySectionPlaylistCopyWith<$Res> { - factory $SpotifySectionPlaylistCopyWith(SpotifySectionPlaylist value, - $Res Function(SpotifySectionPlaylist) then) = - _$SpotifySectionPlaylistCopyWithImpl<$Res, SpotifySectionPlaylist>; - @useResult - $Res call( - {String description, - String format, - List images, - String name, - String owner, - String uri}); -} - -/// @nodoc -class _$SpotifySectionPlaylistCopyWithImpl<$Res, - $Val extends SpotifySectionPlaylist> - implements $SpotifySectionPlaylistCopyWith<$Res> { - _$SpotifySectionPlaylistCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifySectionPlaylist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? description = null, - Object? format = null, - Object? images = null, - Object? name = null, - Object? owner = null, - Object? uri = null, - }) { - return _then(_value.copyWith( - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - format: null == format - ? _value.format - : format // ignore: cast_nullable_to_non_nullable - as String, - images: null == images - ? _value.images - : images // ignore: cast_nullable_to_non_nullable - as List, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifySectionPlaylistImplCopyWith<$Res> - implements $SpotifySectionPlaylistCopyWith<$Res> { - factory _$$SpotifySectionPlaylistImplCopyWith( - _$SpotifySectionPlaylistImpl value, - $Res Function(_$SpotifySectionPlaylistImpl) then) = - __$$SpotifySectionPlaylistImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String description, - String format, - List images, - String name, - String owner, - String uri}); -} - -/// @nodoc -class __$$SpotifySectionPlaylistImplCopyWithImpl<$Res> - extends _$SpotifySectionPlaylistCopyWithImpl<$Res, - _$SpotifySectionPlaylistImpl> - implements _$$SpotifySectionPlaylistImplCopyWith<$Res> { - __$$SpotifySectionPlaylistImplCopyWithImpl( - _$SpotifySectionPlaylistImpl _value, - $Res Function(_$SpotifySectionPlaylistImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifySectionPlaylist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? description = null, - Object? format = null, - Object? images = null, - Object? name = null, - Object? owner = null, - Object? uri = null, - }) { - return _then(_$SpotifySectionPlaylistImpl( - description: null == description - ? _value.description - : description // ignore: cast_nullable_to_non_nullable - as String, - format: null == format - ? _value.format - : format // ignore: cast_nullable_to_non_nullable - as String, - images: null == images - ? _value._images - : images // ignore: cast_nullable_to_non_nullable - as List, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - owner: null == owner - ? _value.owner - : owner // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifySectionPlaylistImpl extends _SpotifySectionPlaylist { - const _$SpotifySectionPlaylistImpl( - {required this.description, - required this.format, - required final List images, - required this.name, - required this.owner, - required this.uri}) - : _images = images, - super._(); - - factory _$SpotifySectionPlaylistImpl.fromJson(Map json) => - _$$SpotifySectionPlaylistImplFromJson(json); - - @override - final String description; - @override - final String format; - final List _images; - @override - List get images { - if (_images is EqualUnmodifiableListView) return _images; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_images); - } - - @override - final String name; - @override - final String owner; - @override - final String uri; - - @override - String toString() { - return 'SpotifySectionPlaylist(description: $description, format: $format, images: $images, name: $name, owner: $owner, uri: $uri)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifySectionPlaylistImpl && - (identical(other.description, description) || - other.description == description) && - (identical(other.format, format) || other.format == format) && - const DeepCollectionEquality().equals(other._images, _images) && - (identical(other.name, name) || other.name == name) && - (identical(other.owner, owner) || other.owner == owner) && - (identical(other.uri, uri) || other.uri == uri)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, description, format, - const DeepCollectionEquality().hash(_images), name, owner, uri); - - /// Create a copy of SpotifySectionPlaylist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifySectionPlaylistImplCopyWith<_$SpotifySectionPlaylistImpl> - get copyWith => __$$SpotifySectionPlaylistImplCopyWithImpl< - _$SpotifySectionPlaylistImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SpotifySectionPlaylistImplToJson( - this, - ); - } -} - -abstract class _SpotifySectionPlaylist extends SpotifySectionPlaylist { - const factory _SpotifySectionPlaylist( - {required final String description, - required final String format, - required final List images, - required final String name, - required final String owner, - required final String uri}) = _$SpotifySectionPlaylistImpl; - const _SpotifySectionPlaylist._() : super._(); - - factory _SpotifySectionPlaylist.fromJson(Map json) = - _$SpotifySectionPlaylistImpl.fromJson; - - @override - String get description; - @override - String get format; - @override - List get images; - @override - String get name; - @override - String get owner; - @override - String get uri; - - /// Create a copy of SpotifySectionPlaylist - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifySectionPlaylistImplCopyWith<_$SpotifySectionPlaylistImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifySectionArtist _$SpotifySectionArtistFromJson(Map json) { - return _SpotifySectionArtist.fromJson(json); -} - -/// @nodoc -mixin _$SpotifySectionArtist { - String get name => throw _privateConstructorUsedError; - String get uri => throw _privateConstructorUsedError; - List get images => - throw _privateConstructorUsedError; - - /// Serializes this SpotifySectionArtist to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifySectionArtist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifySectionArtistCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifySectionArtistCopyWith<$Res> { - factory $SpotifySectionArtistCopyWith(SpotifySectionArtist value, - $Res Function(SpotifySectionArtist) then) = - _$SpotifySectionArtistCopyWithImpl<$Res, SpotifySectionArtist>; - @useResult - $Res call({String name, String uri, List images}); -} - -/// @nodoc -class _$SpotifySectionArtistCopyWithImpl<$Res, - $Val extends SpotifySectionArtist> - implements $SpotifySectionArtistCopyWith<$Res> { - _$SpotifySectionArtistCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifySectionArtist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? uri = null, - Object? images = null, - }) { - return _then(_value.copyWith( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // 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 _$$SpotifySectionArtistImplCopyWith<$Res> - implements $SpotifySectionArtistCopyWith<$Res> { - factory _$$SpotifySectionArtistImplCopyWith(_$SpotifySectionArtistImpl value, - $Res Function(_$SpotifySectionArtistImpl) then) = - __$$SpotifySectionArtistImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String name, String uri, List images}); -} - -/// @nodoc -class __$$SpotifySectionArtistImplCopyWithImpl<$Res> - extends _$SpotifySectionArtistCopyWithImpl<$Res, _$SpotifySectionArtistImpl> - implements _$$SpotifySectionArtistImplCopyWith<$Res> { - __$$SpotifySectionArtistImplCopyWithImpl(_$SpotifySectionArtistImpl _value, - $Res Function(_$SpotifySectionArtistImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifySectionArtist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? uri = null, - Object? images = null, - }) { - return _then(_$SpotifySectionArtistImpl( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // 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 _$SpotifySectionArtistImpl extends _SpotifySectionArtist { - const _$SpotifySectionArtistImpl( - {required this.name, - required this.uri, - required final List images}) - : _images = images, - super._(); - - factory _$SpotifySectionArtistImpl.fromJson(Map json) => - _$$SpotifySectionArtistImplFromJson(json); - - @override - final String name; - @override - final String uri; - final List _images; - @override - List get images { - if (_images is EqualUnmodifiableListView) return _images; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_images); - } - - @override - String toString() { - return 'SpotifySectionArtist(name: $name, uri: $uri, images: $images)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifySectionArtistImpl && - (identical(other.name, name) || other.name == name) && - (identical(other.uri, uri) || other.uri == uri) && - const DeepCollectionEquality().equals(other._images, _images)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, name, uri, const DeepCollectionEquality().hash(_images)); - - /// Create a copy of SpotifySectionArtist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifySectionArtistImplCopyWith<_$SpotifySectionArtistImpl> - get copyWith => - __$$SpotifySectionArtistImplCopyWithImpl<_$SpotifySectionArtistImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$SpotifySectionArtistImplToJson( - this, - ); - } -} - -abstract class _SpotifySectionArtist extends SpotifySectionArtist { - const factory _SpotifySectionArtist( - {required final String name, - required final String uri, - required final List images}) = - _$SpotifySectionArtistImpl; - const _SpotifySectionArtist._() : super._(); - - factory _SpotifySectionArtist.fromJson(Map json) = - _$SpotifySectionArtistImpl.fromJson; - - @override - String get name; - @override - String get uri; - @override - List get images; - - /// Create a copy of SpotifySectionArtist - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifySectionArtistImplCopyWith<_$SpotifySectionArtistImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifySectionAlbum _$SpotifySectionAlbumFromJson(Map json) { - return _SpotifySectionAlbum.fromJson(json); -} - -/// @nodoc -mixin _$SpotifySectionAlbum { - List get artists => - throw _privateConstructorUsedError; - List get images => - throw _privateConstructorUsedError; - String get name => throw _privateConstructorUsedError; - String get uri => throw _privateConstructorUsedError; - - /// Serializes this SpotifySectionAlbum to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifySectionAlbum - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifySectionAlbumCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifySectionAlbumCopyWith<$Res> { - factory $SpotifySectionAlbumCopyWith( - SpotifySectionAlbum value, $Res Function(SpotifySectionAlbum) then) = - _$SpotifySectionAlbumCopyWithImpl<$Res, SpotifySectionAlbum>; - @useResult - $Res call( - {List artists, - List images, - String name, - String uri}); -} - -/// @nodoc -class _$SpotifySectionAlbumCopyWithImpl<$Res, $Val extends SpotifySectionAlbum> - implements $SpotifySectionAlbumCopyWith<$Res> { - _$SpotifySectionAlbumCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifySectionAlbum - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? artists = null, - Object? images = null, - Object? name = null, - Object? uri = null, - }) { - return _then(_value.copyWith( - artists: null == artists - ? _value.artists - : artists // ignore: cast_nullable_to_non_nullable - as List, - images: null == images - ? _value.images - : images // ignore: cast_nullable_to_non_nullable - as List, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifySectionAlbumImplCopyWith<$Res> - implements $SpotifySectionAlbumCopyWith<$Res> { - factory _$$SpotifySectionAlbumImplCopyWith(_$SpotifySectionAlbumImpl value, - $Res Function(_$SpotifySectionAlbumImpl) then) = - __$$SpotifySectionAlbumImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {List artists, - List images, - String name, - String uri}); -} - -/// @nodoc -class __$$SpotifySectionAlbumImplCopyWithImpl<$Res> - extends _$SpotifySectionAlbumCopyWithImpl<$Res, _$SpotifySectionAlbumImpl> - implements _$$SpotifySectionAlbumImplCopyWith<$Res> { - __$$SpotifySectionAlbumImplCopyWithImpl(_$SpotifySectionAlbumImpl _value, - $Res Function(_$SpotifySectionAlbumImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifySectionAlbum - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? artists = null, - Object? images = null, - Object? name = null, - Object? uri = null, - }) { - return _then(_$SpotifySectionAlbumImpl( - artists: null == artists - ? _value._artists - : artists // ignore: cast_nullable_to_non_nullable - as List, - images: null == images - ? _value._images - : images // ignore: cast_nullable_to_non_nullable - as List, - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifySectionAlbumImpl extends _SpotifySectionAlbum { - const _$SpotifySectionAlbumImpl( - {required final List artists, - required final List images, - required this.name, - required this.uri}) - : _artists = artists, - _images = images, - super._(); - - factory _$SpotifySectionAlbumImpl.fromJson(Map json) => - _$$SpotifySectionAlbumImplFromJson(json); - - final List _artists; - @override - List get artists { - if (_artists is EqualUnmodifiableListView) return _artists; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_artists); - } - - final List _images; - @override - List get images { - if (_images is EqualUnmodifiableListView) return _images; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_images); - } - - @override - final String name; - @override - final String uri; - - @override - String toString() { - return 'SpotifySectionAlbum(artists: $artists, images: $images, name: $name, uri: $uri)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifySectionAlbumImpl && - const DeepCollectionEquality().equals(other._artists, _artists) && - const DeepCollectionEquality().equals(other._images, _images) && - (identical(other.name, name) || other.name == name) && - (identical(other.uri, uri) || other.uri == uri)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(_artists), - const DeepCollectionEquality().hash(_images), - name, - uri); - - /// Create a copy of SpotifySectionAlbum - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifySectionAlbumImplCopyWith<_$SpotifySectionAlbumImpl> get copyWith => - __$$SpotifySectionAlbumImplCopyWithImpl<_$SpotifySectionAlbumImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$SpotifySectionAlbumImplToJson( - this, - ); - } -} - -abstract class _SpotifySectionAlbum extends SpotifySectionAlbum { - const factory _SpotifySectionAlbum( - {required final List artists, - required final List images, - required final String name, - required final String uri}) = _$SpotifySectionAlbumImpl; - const _SpotifySectionAlbum._() : super._(); - - factory _SpotifySectionAlbum.fromJson(Map json) = - _$SpotifySectionAlbumImpl.fromJson; - - @override - List get artists; - @override - List get images; - @override - String get name; - @override - String get uri; - - /// Create a copy of SpotifySectionAlbum - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifySectionAlbumImplCopyWith<_$SpotifySectionAlbumImpl> get copyWith => - throw _privateConstructorUsedError; -} - -SpotifySectionAlbumArtist _$SpotifySectionAlbumArtistFromJson( - Map json) { - return _SpotifySectionAlbumArtist.fromJson(json); -} - -/// @nodoc -mixin _$SpotifySectionAlbumArtist { - String get name => throw _privateConstructorUsedError; - String get uri => throw _privateConstructorUsedError; - - /// Serializes this SpotifySectionAlbumArtist to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifySectionAlbumArtist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifySectionAlbumArtistCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifySectionAlbumArtistCopyWith<$Res> { - factory $SpotifySectionAlbumArtistCopyWith(SpotifySectionAlbumArtist value, - $Res Function(SpotifySectionAlbumArtist) then) = - _$SpotifySectionAlbumArtistCopyWithImpl<$Res, SpotifySectionAlbumArtist>; - @useResult - $Res call({String name, String uri}); -} - -/// @nodoc -class _$SpotifySectionAlbumArtistCopyWithImpl<$Res, - $Val extends SpotifySectionAlbumArtist> - implements $SpotifySectionAlbumArtistCopyWith<$Res> { - _$SpotifySectionAlbumArtistCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifySectionAlbumArtist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? uri = null, - }) { - return _then(_value.copyWith( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifySectionAlbumArtistImplCopyWith<$Res> - implements $SpotifySectionAlbumArtistCopyWith<$Res> { - factory _$$SpotifySectionAlbumArtistImplCopyWith( - _$SpotifySectionAlbumArtistImpl value, - $Res Function(_$SpotifySectionAlbumArtistImpl) then) = - __$$SpotifySectionAlbumArtistImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String name, String uri}); -} - -/// @nodoc -class __$$SpotifySectionAlbumArtistImplCopyWithImpl<$Res> - extends _$SpotifySectionAlbumArtistCopyWithImpl<$Res, - _$SpotifySectionAlbumArtistImpl> - implements _$$SpotifySectionAlbumArtistImplCopyWith<$Res> { - __$$SpotifySectionAlbumArtistImplCopyWithImpl( - _$SpotifySectionAlbumArtistImpl _value, - $Res Function(_$SpotifySectionAlbumArtistImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifySectionAlbumArtist - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? name = null, - Object? uri = null, - }) { - return _then(_$SpotifySectionAlbumArtistImpl( - name: null == name - ? _value.name - : name // ignore: cast_nullable_to_non_nullable - as String, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifySectionAlbumArtistImpl extends _SpotifySectionAlbumArtist { - const _$SpotifySectionAlbumArtistImpl({required this.name, required this.uri}) - : super._(); - - factory _$SpotifySectionAlbumArtistImpl.fromJson(Map json) => - _$$SpotifySectionAlbumArtistImplFromJson(json); - - @override - final String name; - @override - final String uri; - - @override - String toString() { - return 'SpotifySectionAlbumArtist(name: $name, uri: $uri)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifySectionAlbumArtistImpl && - (identical(other.name, name) || other.name == name) && - (identical(other.uri, uri) || other.uri == uri)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, name, uri); - - /// Create a copy of SpotifySectionAlbumArtist - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifySectionAlbumArtistImplCopyWith<_$SpotifySectionAlbumArtistImpl> - get copyWith => __$$SpotifySectionAlbumArtistImplCopyWithImpl< - _$SpotifySectionAlbumArtistImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SpotifySectionAlbumArtistImplToJson( - this, - ); - } -} - -abstract class _SpotifySectionAlbumArtist extends SpotifySectionAlbumArtist { - const factory _SpotifySectionAlbumArtist( - {required final String name, - required final String uri}) = _$SpotifySectionAlbumArtistImpl; - const _SpotifySectionAlbumArtist._() : super._(); - - factory _SpotifySectionAlbumArtist.fromJson(Map json) = - _$SpotifySectionAlbumArtistImpl.fromJson; - - @override - String get name; - @override - String get uri; - - /// Create a copy of SpotifySectionAlbumArtist - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifySectionAlbumArtistImplCopyWith<_$SpotifySectionAlbumArtistImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifySectionItemImage _$SpotifySectionItemImageFromJson( - Map json) { - return _SpotifySectionItemImage.fromJson(json); -} - -/// @nodoc -mixin _$SpotifySectionItemImage { - num? get height => throw _privateConstructorUsedError; - String get url => throw _privateConstructorUsedError; - num? get width => throw _privateConstructorUsedError; - - /// Serializes this SpotifySectionItemImage to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifySectionItemImage - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifySectionItemImageCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifySectionItemImageCopyWith<$Res> { - factory $SpotifySectionItemImageCopyWith(SpotifySectionItemImage value, - $Res Function(SpotifySectionItemImage) then) = - _$SpotifySectionItemImageCopyWithImpl<$Res, SpotifySectionItemImage>; - @useResult - $Res call({num? height, String url, num? width}); -} - -/// @nodoc -class _$SpotifySectionItemImageCopyWithImpl<$Res, - $Val extends SpotifySectionItemImage> - implements $SpotifySectionItemImageCopyWith<$Res> { - _$SpotifySectionItemImageCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifySectionItemImage - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? height = freezed, - Object? url = null, - Object? width = freezed, - }) { - return _then(_value.copyWith( - height: freezed == height - ? _value.height - : height // ignore: cast_nullable_to_non_nullable - as num?, - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - width: freezed == width - ? _value.width - : width // ignore: cast_nullable_to_non_nullable - as num?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifySectionItemImageImplCopyWith<$Res> - implements $SpotifySectionItemImageCopyWith<$Res> { - factory _$$SpotifySectionItemImageImplCopyWith( - _$SpotifySectionItemImageImpl value, - $Res Function(_$SpotifySectionItemImageImpl) then) = - __$$SpotifySectionItemImageImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({num? height, String url, num? width}); -} - -/// @nodoc -class __$$SpotifySectionItemImageImplCopyWithImpl<$Res> - extends _$SpotifySectionItemImageCopyWithImpl<$Res, - _$SpotifySectionItemImageImpl> - implements _$$SpotifySectionItemImageImplCopyWith<$Res> { - __$$SpotifySectionItemImageImplCopyWithImpl( - _$SpotifySectionItemImageImpl _value, - $Res Function(_$SpotifySectionItemImageImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifySectionItemImage - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? height = freezed, - Object? url = null, - Object? width = freezed, - }) { - return _then(_$SpotifySectionItemImageImpl( - height: freezed == height - ? _value.height - : height // ignore: cast_nullable_to_non_nullable - as num?, - url: null == url - ? _value.url - : url // ignore: cast_nullable_to_non_nullable - as String, - width: freezed == width - ? _value.width - : width // ignore: cast_nullable_to_non_nullable - as num?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifySectionItemImageImpl extends _SpotifySectionItemImage { - const _$SpotifySectionItemImageImpl( - {required this.height, required this.url, required this.width}) - : super._(); - - factory _$SpotifySectionItemImageImpl.fromJson(Map json) => - _$$SpotifySectionItemImageImplFromJson(json); - - @override - final num? height; - @override - final String url; - @override - final num? width; - - @override - String toString() { - return 'SpotifySectionItemImage(height: $height, url: $url, width: $width)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifySectionItemImageImpl && - (identical(other.height, height) || other.height == height) && - (identical(other.url, url) || other.url == url) && - (identical(other.width, width) || other.width == width)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, height, url, width); - - /// Create a copy of SpotifySectionItemImage - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifySectionItemImageImplCopyWith<_$SpotifySectionItemImageImpl> - get copyWith => __$$SpotifySectionItemImageImplCopyWithImpl< - _$SpotifySectionItemImageImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SpotifySectionItemImageImplToJson( - this, - ); - } -} - -abstract class _SpotifySectionItemImage extends SpotifySectionItemImage { - const factory _SpotifySectionItemImage( - {required final num? height, - required final String url, - required final num? width}) = _$SpotifySectionItemImageImpl; - const _SpotifySectionItemImage._() : super._(); - - factory _SpotifySectionItemImage.fromJson(Map json) = - _$SpotifySectionItemImageImpl.fromJson; - - @override - num? get height; - @override - String get url; - @override - num? get width; - - /// Create a copy of SpotifySectionItemImage - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifySectionItemImageImplCopyWith<_$SpotifySectionItemImageImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifyHomeFeedSectionItem _$SpotifyHomeFeedSectionItemFromJson( - Map json) { - return _SpotifyHomeFeedSectionItem.fromJson(json); -} - -/// @nodoc -mixin _$SpotifyHomeFeedSectionItem { - String get typename => throw _privateConstructorUsedError; - SpotifySectionPlaylist? get playlist => throw _privateConstructorUsedError; - SpotifySectionArtist? get artist => throw _privateConstructorUsedError; - SpotifySectionAlbum? get album => throw _privateConstructorUsedError; - - /// Serializes this SpotifyHomeFeedSectionItem to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifyHomeFeedSectionItemCopyWith - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifyHomeFeedSectionItemCopyWith<$Res> { - factory $SpotifyHomeFeedSectionItemCopyWith(SpotifyHomeFeedSectionItem value, - $Res Function(SpotifyHomeFeedSectionItem) then) = - _$SpotifyHomeFeedSectionItemCopyWithImpl<$Res, - SpotifyHomeFeedSectionItem>; - @useResult - $Res call( - {String typename, - SpotifySectionPlaylist? playlist, - SpotifySectionArtist? artist, - SpotifySectionAlbum? album}); - - $SpotifySectionPlaylistCopyWith<$Res>? get playlist; - $SpotifySectionArtistCopyWith<$Res>? get artist; - $SpotifySectionAlbumCopyWith<$Res>? get album; -} - -/// @nodoc -class _$SpotifyHomeFeedSectionItemCopyWithImpl<$Res, - $Val extends SpotifyHomeFeedSectionItem> - implements $SpotifyHomeFeedSectionItemCopyWith<$Res> { - _$SpotifyHomeFeedSectionItemCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? typename = null, - Object? playlist = freezed, - Object? artist = freezed, - Object? album = freezed, - }) { - return _then(_value.copyWith( - typename: null == typename - ? _value.typename - : typename // ignore: cast_nullable_to_non_nullable - as String, - playlist: freezed == playlist - ? _value.playlist - : playlist // ignore: cast_nullable_to_non_nullable - as SpotifySectionPlaylist?, - artist: freezed == artist - ? _value.artist - : artist // ignore: cast_nullable_to_non_nullable - as SpotifySectionArtist?, - album: freezed == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as SpotifySectionAlbum?, - ) as $Val); - } - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SpotifySectionPlaylistCopyWith<$Res>? get playlist { - if (_value.playlist == null) { - return null; - } - - return $SpotifySectionPlaylistCopyWith<$Res>(_value.playlist!, (value) { - return _then(_value.copyWith(playlist: value) as $Val); - }); - } - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SpotifySectionArtistCopyWith<$Res>? get artist { - if (_value.artist == null) { - return null; - } - - return $SpotifySectionArtistCopyWith<$Res>(_value.artist!, (value) { - return _then(_value.copyWith(artist: value) as $Val); - }); - } - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $SpotifySectionAlbumCopyWith<$Res>? get album { - if (_value.album == null) { - return null; - } - - return $SpotifySectionAlbumCopyWith<$Res>(_value.album!, (value) { - return _then(_value.copyWith(album: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$SpotifyHomeFeedSectionItemImplCopyWith<$Res> - implements $SpotifyHomeFeedSectionItemCopyWith<$Res> { - factory _$$SpotifyHomeFeedSectionItemImplCopyWith( - _$SpotifyHomeFeedSectionItemImpl value, - $Res Function(_$SpotifyHomeFeedSectionItemImpl) then) = - __$$SpotifyHomeFeedSectionItemImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String typename, - SpotifySectionPlaylist? playlist, - SpotifySectionArtist? artist, - SpotifySectionAlbum? album}); - - @override - $SpotifySectionPlaylistCopyWith<$Res>? get playlist; - @override - $SpotifySectionArtistCopyWith<$Res>? get artist; - @override - $SpotifySectionAlbumCopyWith<$Res>? get album; -} - -/// @nodoc -class __$$SpotifyHomeFeedSectionItemImplCopyWithImpl<$Res> - extends _$SpotifyHomeFeedSectionItemCopyWithImpl<$Res, - _$SpotifyHomeFeedSectionItemImpl> - implements _$$SpotifyHomeFeedSectionItemImplCopyWith<$Res> { - __$$SpotifyHomeFeedSectionItemImplCopyWithImpl( - _$SpotifyHomeFeedSectionItemImpl _value, - $Res Function(_$SpotifyHomeFeedSectionItemImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? typename = null, - Object? playlist = freezed, - Object? artist = freezed, - Object? album = freezed, - }) { - return _then(_$SpotifyHomeFeedSectionItemImpl( - typename: null == typename - ? _value.typename - : typename // ignore: cast_nullable_to_non_nullable - as String, - playlist: freezed == playlist - ? _value.playlist - : playlist // ignore: cast_nullable_to_non_nullable - as SpotifySectionPlaylist?, - artist: freezed == artist - ? _value.artist - : artist // ignore: cast_nullable_to_non_nullable - as SpotifySectionArtist?, - album: freezed == album - ? _value.album - : album // ignore: cast_nullable_to_non_nullable - as SpotifySectionAlbum?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifyHomeFeedSectionItemImpl implements _SpotifyHomeFeedSectionItem { - _$SpotifyHomeFeedSectionItemImpl( - {required this.typename, this.playlist, this.artist, this.album}); - - factory _$SpotifyHomeFeedSectionItemImpl.fromJson( - Map json) => - _$$SpotifyHomeFeedSectionItemImplFromJson(json); - - @override - final String typename; - @override - final SpotifySectionPlaylist? playlist; - @override - final SpotifySectionArtist? artist; - @override - final SpotifySectionAlbum? album; - - @override - String toString() { - return 'SpotifyHomeFeedSectionItem(typename: $typename, playlist: $playlist, artist: $artist, album: $album)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifyHomeFeedSectionItemImpl && - (identical(other.typename, typename) || - other.typename == typename) && - (identical(other.playlist, playlist) || - other.playlist == playlist) && - (identical(other.artist, artist) || other.artist == artist) && - (identical(other.album, album) || other.album == album)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => - Object.hash(runtimeType, typename, playlist, artist, album); - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifyHomeFeedSectionItemImplCopyWith<_$SpotifyHomeFeedSectionItemImpl> - get copyWith => __$$SpotifyHomeFeedSectionItemImplCopyWithImpl< - _$SpotifyHomeFeedSectionItemImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SpotifyHomeFeedSectionItemImplToJson( - this, - ); - } -} - -abstract class _SpotifyHomeFeedSectionItem - implements SpotifyHomeFeedSectionItem { - factory _SpotifyHomeFeedSectionItem( - {required final String typename, - final SpotifySectionPlaylist? playlist, - final SpotifySectionArtist? artist, - final SpotifySectionAlbum? album}) = _$SpotifyHomeFeedSectionItemImpl; - - factory _SpotifyHomeFeedSectionItem.fromJson(Map json) = - _$SpotifyHomeFeedSectionItemImpl.fromJson; - - @override - String get typename; - @override - SpotifySectionPlaylist? get playlist; - @override - SpotifySectionArtist? get artist; - @override - SpotifySectionAlbum? get album; - - /// Create a copy of SpotifyHomeFeedSectionItem - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifyHomeFeedSectionItemImplCopyWith<_$SpotifyHomeFeedSectionItemImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifyHomeFeedSection _$SpotifyHomeFeedSectionFromJson( - Map json) { - return _SpotifyHomeFeedSection.fromJson(json); -} - -/// @nodoc -mixin _$SpotifyHomeFeedSection { - String get typename => throw _privateConstructorUsedError; - String? get title => throw _privateConstructorUsedError; - String get uri => throw _privateConstructorUsedError; - List get items => - throw _privateConstructorUsedError; - - /// Serializes this SpotifyHomeFeedSection to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifyHomeFeedSection - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifyHomeFeedSectionCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifyHomeFeedSectionCopyWith<$Res> { - factory $SpotifyHomeFeedSectionCopyWith(SpotifyHomeFeedSection value, - $Res Function(SpotifyHomeFeedSection) then) = - _$SpotifyHomeFeedSectionCopyWithImpl<$Res, SpotifyHomeFeedSection>; - @useResult - $Res call( - {String typename, - String? title, - String uri, - List items}); -} - -/// @nodoc -class _$SpotifyHomeFeedSectionCopyWithImpl<$Res, - $Val extends SpotifyHomeFeedSection> - implements $SpotifyHomeFeedSectionCopyWith<$Res> { - _$SpotifyHomeFeedSectionCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifyHomeFeedSection - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? typename = null, - Object? title = freezed, - Object? uri = null, - Object? items = null, - }) { - return _then(_value.copyWith( - typename: null == typename - ? _value.typename - : typename // ignore: cast_nullable_to_non_nullable - as String, - title: freezed == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String?, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - items: null == items - ? _value.items - : items // ignore: cast_nullable_to_non_nullable - as List, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifyHomeFeedSectionImplCopyWith<$Res> - implements $SpotifyHomeFeedSectionCopyWith<$Res> { - factory _$$SpotifyHomeFeedSectionImplCopyWith( - _$SpotifyHomeFeedSectionImpl value, - $Res Function(_$SpotifyHomeFeedSectionImpl) then) = - __$$SpotifyHomeFeedSectionImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {String typename, - String? title, - String uri, - List items}); -} - -/// @nodoc -class __$$SpotifyHomeFeedSectionImplCopyWithImpl<$Res> - extends _$SpotifyHomeFeedSectionCopyWithImpl<$Res, - _$SpotifyHomeFeedSectionImpl> - implements _$$SpotifyHomeFeedSectionImplCopyWith<$Res> { - __$$SpotifyHomeFeedSectionImplCopyWithImpl( - _$SpotifyHomeFeedSectionImpl _value, - $Res Function(_$SpotifyHomeFeedSectionImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifyHomeFeedSection - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? typename = null, - Object? title = freezed, - Object? uri = null, - Object? items = null, - }) { - return _then(_$SpotifyHomeFeedSectionImpl( - typename: null == typename - ? _value.typename - : typename // ignore: cast_nullable_to_non_nullable - as String, - title: freezed == title - ? _value.title - : title // ignore: cast_nullable_to_non_nullable - as String?, - uri: null == uri - ? _value.uri - : uri // ignore: cast_nullable_to_non_nullable - as String, - items: null == items - ? _value._items - : items // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifyHomeFeedSectionImpl implements _SpotifyHomeFeedSection { - _$SpotifyHomeFeedSectionImpl( - {required this.typename, - this.title, - required this.uri, - required final List items}) - : _items = items; - - factory _$SpotifyHomeFeedSectionImpl.fromJson(Map json) => - _$$SpotifyHomeFeedSectionImplFromJson(json); - - @override - final String typename; - @override - final String? title; - @override - final String uri; - final List _items; - @override - List get items { - if (_items is EqualUnmodifiableListView) return _items; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_items); - } - - @override - String toString() { - return 'SpotifyHomeFeedSection(typename: $typename, title: $title, uri: $uri, items: $items)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifyHomeFeedSectionImpl && - (identical(other.typename, typename) || - other.typename == typename) && - (identical(other.title, title) || other.title == title) && - (identical(other.uri, uri) || other.uri == uri) && - const DeepCollectionEquality().equals(other._items, _items)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash(runtimeType, typename, title, uri, - const DeepCollectionEquality().hash(_items)); - - /// Create a copy of SpotifyHomeFeedSection - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifyHomeFeedSectionImplCopyWith<_$SpotifyHomeFeedSectionImpl> - get copyWith => __$$SpotifyHomeFeedSectionImplCopyWithImpl< - _$SpotifyHomeFeedSectionImpl>(this, _$identity); - - @override - Map toJson() { - return _$$SpotifyHomeFeedSectionImplToJson( - this, - ); - } -} - -abstract class _SpotifyHomeFeedSection implements SpotifyHomeFeedSection { - factory _SpotifyHomeFeedSection( - {required final String typename, - final String? title, - required final String uri, - required final List items}) = - _$SpotifyHomeFeedSectionImpl; - - factory _SpotifyHomeFeedSection.fromJson(Map json) = - _$SpotifyHomeFeedSectionImpl.fromJson; - - @override - String get typename; - @override - String? get title; - @override - String get uri; - @override - List get items; - - /// Create a copy of SpotifyHomeFeedSection - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifyHomeFeedSectionImplCopyWith<_$SpotifyHomeFeedSectionImpl> - get copyWith => throw _privateConstructorUsedError; -} - -SpotifyHomeFeed _$SpotifyHomeFeedFromJson(Map json) { - return _SpotifyHomeFeed.fromJson(json); -} - -/// @nodoc -mixin _$SpotifyHomeFeed { - String get greeting => throw _privateConstructorUsedError; - List get sections => - throw _privateConstructorUsedError; - - /// Serializes this SpotifyHomeFeed to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of SpotifyHomeFeed - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $SpotifyHomeFeedCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $SpotifyHomeFeedCopyWith<$Res> { - factory $SpotifyHomeFeedCopyWith( - SpotifyHomeFeed value, $Res Function(SpotifyHomeFeed) then) = - _$SpotifyHomeFeedCopyWithImpl<$Res, SpotifyHomeFeed>; - @useResult - $Res call({String greeting, List sections}); -} - -/// @nodoc -class _$SpotifyHomeFeedCopyWithImpl<$Res, $Val extends SpotifyHomeFeed> - implements $SpotifyHomeFeedCopyWith<$Res> { - _$SpotifyHomeFeedCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of SpotifyHomeFeed - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? greeting = null, - Object? sections = null, - }) { - return _then(_value.copyWith( - greeting: null == greeting - ? _value.greeting - : greeting // ignore: cast_nullable_to_non_nullable - as String, - sections: null == sections - ? _value.sections - : sections // ignore: cast_nullable_to_non_nullable - as List, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$SpotifyHomeFeedImplCopyWith<$Res> - implements $SpotifyHomeFeedCopyWith<$Res> { - factory _$$SpotifyHomeFeedImplCopyWith(_$SpotifyHomeFeedImpl value, - $Res Function(_$SpotifyHomeFeedImpl) then) = - __$$SpotifyHomeFeedImplCopyWithImpl<$Res>; - @override - @useResult - $Res call({String greeting, List sections}); -} - -/// @nodoc -class __$$SpotifyHomeFeedImplCopyWithImpl<$Res> - extends _$SpotifyHomeFeedCopyWithImpl<$Res, _$SpotifyHomeFeedImpl> - implements _$$SpotifyHomeFeedImplCopyWith<$Res> { - __$$SpotifyHomeFeedImplCopyWithImpl( - _$SpotifyHomeFeedImpl _value, $Res Function(_$SpotifyHomeFeedImpl) _then) - : super(_value, _then); - - /// Create a copy of SpotifyHomeFeed - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? greeting = null, - Object? sections = null, - }) { - return _then(_$SpotifyHomeFeedImpl( - greeting: null == greeting - ? _value.greeting - : greeting // ignore: cast_nullable_to_non_nullable - as String, - sections: null == sections - ? _value._sections - : sections // ignore: cast_nullable_to_non_nullable - as List, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$SpotifyHomeFeedImpl implements _SpotifyHomeFeed { - _$SpotifyHomeFeedImpl( - {required this.greeting, - required final List sections}) - : _sections = sections; - - factory _$SpotifyHomeFeedImpl.fromJson(Map json) => - _$$SpotifyHomeFeedImplFromJson(json); - - @override - final String greeting; - final List _sections; - @override - List get sections { - if (_sections is EqualUnmodifiableListView) return _sections; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_sections); - } - - @override - String toString() { - return 'SpotifyHomeFeed(greeting: $greeting, sections: $sections)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$SpotifyHomeFeedImpl && - (identical(other.greeting, greeting) || - other.greeting == greeting) && - const DeepCollectionEquality().equals(other._sections, _sections)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, greeting, const DeepCollectionEquality().hash(_sections)); - - /// Create a copy of SpotifyHomeFeed - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$SpotifyHomeFeedImplCopyWith<_$SpotifyHomeFeedImpl> get copyWith => - __$$SpotifyHomeFeedImplCopyWithImpl<_$SpotifyHomeFeedImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$SpotifyHomeFeedImplToJson( - this, - ); - } -} - -abstract class _SpotifyHomeFeed implements SpotifyHomeFeed { - factory _SpotifyHomeFeed( - {required final String greeting, - required final List sections}) = - _$SpotifyHomeFeedImpl; - - factory _SpotifyHomeFeed.fromJson(Map json) = - _$SpotifyHomeFeedImpl.fromJson; - - @override - String get greeting; - @override - List get sections; - - /// Create a copy of SpotifyHomeFeed - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$SpotifyHomeFeedImplCopyWith<_$SpotifyHomeFeedImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/spotify/home_feed.g.dart b/lib/models/spotify/home_feed.g.dart deleted file mode 100644 index fceb3db4..00000000 --- a/lib/models/spotify/home_feed.g.dart +++ /dev/null @@ -1,165 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'home_feed.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$SpotifySectionPlaylistImpl _$$SpotifySectionPlaylistImplFromJson(Map json) => - _$SpotifySectionPlaylistImpl( - description: json['description'] as String, - format: json['format'] as String, - images: (json['images'] as List) - .map((e) => SpotifySectionItemImage.fromJson( - Map.from(e as Map))) - .toList(), - name: json['name'] as String, - owner: json['owner'] as String, - uri: json['uri'] as String, - ); - -Map _$$SpotifySectionPlaylistImplToJson( - _$SpotifySectionPlaylistImpl instance) => - { - 'description': instance.description, - 'format': instance.format, - 'images': instance.images.map((e) => e.toJson()).toList(), - 'name': instance.name, - 'owner': instance.owner, - 'uri': instance.uri, - }; - -_$SpotifySectionArtistImpl _$$SpotifySectionArtistImplFromJson(Map json) => - _$SpotifySectionArtistImpl( - name: json['name'] as String, - uri: json['uri'] as String, - images: (json['images'] as List) - .map((e) => SpotifySectionItemImage.fromJson( - Map.from(e as Map))) - .toList(), - ); - -Map _$$SpotifySectionArtistImplToJson( - _$SpotifySectionArtistImpl instance) => - { - 'name': instance.name, - 'uri': instance.uri, - 'images': instance.images.map((e) => e.toJson()).toList(), - }; - -_$SpotifySectionAlbumImpl _$$SpotifySectionAlbumImplFromJson(Map json) => - _$SpotifySectionAlbumImpl( - artists: (json['artists'] as List) - .map((e) => SpotifySectionAlbumArtist.fromJson( - Map.from(e as Map))) - .toList(), - images: (json['images'] as List) - .map((e) => SpotifySectionItemImage.fromJson( - Map.from(e as Map))) - .toList(), - name: json['name'] as String, - uri: json['uri'] as String, - ); - -Map _$$SpotifySectionAlbumImplToJson( - _$SpotifySectionAlbumImpl instance) => - { - '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 json) => - _$SpotifySectionAlbumArtistImpl( - name: json['name'] as String, - uri: json['uri'] as String, - ); - -Map _$$SpotifySectionAlbumArtistImplToJson( - _$SpotifySectionAlbumArtistImpl instance) => - { - 'name': instance.name, - 'uri': instance.uri, - }; - -_$SpotifySectionItemImageImpl _$$SpotifySectionItemImageImplFromJson( - Map json) => - _$SpotifySectionItemImageImpl( - height: json['height'] as num?, - url: json['url'] as String, - width: json['width'] as num?, - ); - -Map _$$SpotifySectionItemImageImplToJson( - _$SpotifySectionItemImageImpl instance) => - { - 'height': instance.height, - 'url': instance.url, - 'width': instance.width, - }; - -_$SpotifyHomeFeedSectionItemImpl _$$SpotifyHomeFeedSectionItemImplFromJson( - Map json) => - _$SpotifyHomeFeedSectionItemImpl( - typename: json['typename'] as String, - playlist: json['playlist'] == null - ? null - : SpotifySectionPlaylist.fromJson( - Map.from(json['playlist'] as Map)), - artist: json['artist'] == null - ? null - : SpotifySectionArtist.fromJson( - Map.from(json['artist'] as Map)), - album: json['album'] == null - ? null - : SpotifySectionAlbum.fromJson( - Map.from(json['album'] as Map)), - ); - -Map _$$SpotifyHomeFeedSectionItemImplToJson( - _$SpotifyHomeFeedSectionItemImpl instance) => - { - 'typename': instance.typename, - 'playlist': instance.playlist?.toJson(), - 'artist': instance.artist?.toJson(), - 'album': instance.album?.toJson(), - }; - -_$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) - .map((e) => SpotifyHomeFeedSectionItem.fromJson( - Map.from(e as Map))) - .toList(), - ); - -Map _$$SpotifyHomeFeedSectionImplToJson( - _$SpotifyHomeFeedSectionImpl instance) => - { - 'typename': instance.typename, - 'title': instance.title, - 'uri': instance.uri, - 'items': instance.items.map((e) => e.toJson()).toList(), - }; - -_$SpotifyHomeFeedImpl _$$SpotifyHomeFeedImplFromJson(Map json) => - _$SpotifyHomeFeedImpl( - greeting: json['greeting'] as String, - sections: (json['sections'] as List) - .map((e) => SpotifyHomeFeedSection.fromJson( - Map.from(e as Map))) - .toList(), - ); - -Map _$$SpotifyHomeFeedImplToJson( - _$SpotifyHomeFeedImpl instance) => - { - 'greeting': instance.greeting, - 'sections': instance.sections.map((e) => e.toJson()).toList(), - }; diff --git a/lib/models/spotify/recommendation_seeds.dart b/lib/models/spotify/recommendation_seeds.dart deleted file mode 100644 index 0d874ad6..00000000 --- a/lib/models/spotify/recommendation_seeds.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'recommendation_seeds.freezed.dart'; -part 'recommendation_seeds.g.dart'; - -@freezed -class GeneratePlaylistProviderInput with _$GeneratePlaylistProviderInput { - factory GeneratePlaylistProviderInput({ - Iterable? seedArtists, - Iterable? seedGenres, - Iterable? seedTracks, - required int limit, - RecommendationSeeds? max, - RecommendationSeeds? min, - RecommendationSeeds? target, - }) = _GeneratePlaylistProviderInput; -} - -@freezed -class RecommendationSeeds with _$RecommendationSeeds { - factory RecommendationSeeds({ - num? acousticness, - num? danceability, - @JsonKey(name: "duration_ms") num? durationMs, - num? energy, - num? instrumentalness, - num? key, - num? liveness, - num? loudness, - num? mode, - num? popularity, - num? speechiness, - num? tempo, - @JsonKey(name: "time_signature") num? timeSignature, - num? valence, - }) = _RecommendationSeeds; - - factory RecommendationSeeds.fromJson(Map json) => - _$RecommendationSeedsFromJson(json); -} diff --git a/lib/models/spotify/recommendation_seeds.freezed.dart b/lib/models/spotify/recommendation_seeds.freezed.dart deleted file mode 100644 index c55a4134..00000000 --- a/lib/models/spotify/recommendation_seeds.freezed.dart +++ /dev/null @@ -1,786 +0,0 @@ -// 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 'recommendation_seeds.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(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'); - -/// @nodoc -mixin _$GeneratePlaylistProviderInput { - Iterable? get seedArtists => throw _privateConstructorUsedError; - Iterable? get seedGenres => throw _privateConstructorUsedError; - Iterable? get seedTracks => throw _privateConstructorUsedError; - int get limit => throw _privateConstructorUsedError; - RecommendationSeeds? get max => throw _privateConstructorUsedError; - RecommendationSeeds? get min => throw _privateConstructorUsedError; - RecommendationSeeds? get target => throw _privateConstructorUsedError; - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $GeneratePlaylistProviderInputCopyWith - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $GeneratePlaylistProviderInputCopyWith<$Res> { - factory $GeneratePlaylistProviderInputCopyWith( - GeneratePlaylistProviderInput value, - $Res Function(GeneratePlaylistProviderInput) then) = - _$GeneratePlaylistProviderInputCopyWithImpl<$Res, - GeneratePlaylistProviderInput>; - @useResult - $Res call( - {Iterable? seedArtists, - Iterable? seedGenres, - Iterable? seedTracks, - int limit, - RecommendationSeeds? max, - RecommendationSeeds? min, - RecommendationSeeds? target}); - - $RecommendationSeedsCopyWith<$Res>? get max; - $RecommendationSeedsCopyWith<$Res>? get min; - $RecommendationSeedsCopyWith<$Res>? get target; -} - -/// @nodoc -class _$GeneratePlaylistProviderInputCopyWithImpl<$Res, - $Val extends GeneratePlaylistProviderInput> - implements $GeneratePlaylistProviderInputCopyWith<$Res> { - _$GeneratePlaylistProviderInputCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? seedArtists = freezed, - Object? seedGenres = freezed, - Object? seedTracks = freezed, - Object? limit = null, - Object? max = freezed, - Object? min = freezed, - Object? target = freezed, - }) { - return _then(_value.copyWith( - seedArtists: freezed == seedArtists - ? _value.seedArtists - : seedArtists // ignore: cast_nullable_to_non_nullable - as Iterable?, - seedGenres: freezed == seedGenres - ? _value.seedGenres - : seedGenres // ignore: cast_nullable_to_non_nullable - as Iterable?, - seedTracks: freezed == seedTracks - ? _value.seedTracks - : seedTracks // ignore: cast_nullable_to_non_nullable - as Iterable?, - limit: null == limit - ? _value.limit - : limit // ignore: cast_nullable_to_non_nullable - as int, - max: freezed == max - ? _value.max - : max // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - min: freezed == min - ? _value.min - : min // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - target: freezed == target - ? _value.target - : target // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - ) as $Val); - } - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $RecommendationSeedsCopyWith<$Res>? get max { - if (_value.max == null) { - return null; - } - - return $RecommendationSeedsCopyWith<$Res>(_value.max!, (value) { - return _then(_value.copyWith(max: value) as $Val); - }); - } - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $RecommendationSeedsCopyWith<$Res>? get min { - if (_value.min == null) { - return null; - } - - return $RecommendationSeedsCopyWith<$Res>(_value.min!, (value) { - return _then(_value.copyWith(min: value) as $Val); - }); - } - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @override - @pragma('vm:prefer-inline') - $RecommendationSeedsCopyWith<$Res>? get target { - if (_value.target == null) { - return null; - } - - return $RecommendationSeedsCopyWith<$Res>(_value.target!, (value) { - return _then(_value.copyWith(target: value) as $Val); - }); - } -} - -/// @nodoc -abstract class _$$GeneratePlaylistProviderInputImplCopyWith<$Res> - implements $GeneratePlaylistProviderInputCopyWith<$Res> { - factory _$$GeneratePlaylistProviderInputImplCopyWith( - _$GeneratePlaylistProviderInputImpl value, - $Res Function(_$GeneratePlaylistProviderInputImpl) then) = - __$$GeneratePlaylistProviderInputImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {Iterable? seedArtists, - Iterable? seedGenres, - Iterable? seedTracks, - int limit, - RecommendationSeeds? max, - RecommendationSeeds? min, - RecommendationSeeds? target}); - - @override - $RecommendationSeedsCopyWith<$Res>? get max; - @override - $RecommendationSeedsCopyWith<$Res>? get min; - @override - $RecommendationSeedsCopyWith<$Res>? get target; -} - -/// @nodoc -class __$$GeneratePlaylistProviderInputImplCopyWithImpl<$Res> - extends _$GeneratePlaylistProviderInputCopyWithImpl<$Res, - _$GeneratePlaylistProviderInputImpl> - implements _$$GeneratePlaylistProviderInputImplCopyWith<$Res> { - __$$GeneratePlaylistProviderInputImplCopyWithImpl( - _$GeneratePlaylistProviderInputImpl _value, - $Res Function(_$GeneratePlaylistProviderInputImpl) _then) - : super(_value, _then); - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? seedArtists = freezed, - Object? seedGenres = freezed, - Object? seedTracks = freezed, - Object? limit = null, - Object? max = freezed, - Object? min = freezed, - Object? target = freezed, - }) { - return _then(_$GeneratePlaylistProviderInputImpl( - seedArtists: freezed == seedArtists - ? _value.seedArtists - : seedArtists // ignore: cast_nullable_to_non_nullable - as Iterable?, - seedGenres: freezed == seedGenres - ? _value.seedGenres - : seedGenres // ignore: cast_nullable_to_non_nullable - as Iterable?, - seedTracks: freezed == seedTracks - ? _value.seedTracks - : seedTracks // ignore: cast_nullable_to_non_nullable - as Iterable?, - limit: null == limit - ? _value.limit - : limit // ignore: cast_nullable_to_non_nullable - as int, - max: freezed == max - ? _value.max - : max // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - min: freezed == min - ? _value.min - : min // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - target: freezed == target - ? _value.target - : target // ignore: cast_nullable_to_non_nullable - as RecommendationSeeds?, - )); - } -} - -/// @nodoc - -class _$GeneratePlaylistProviderInputImpl - implements _GeneratePlaylistProviderInput { - _$GeneratePlaylistProviderInputImpl( - {this.seedArtists, - this.seedGenres, - this.seedTracks, - required this.limit, - this.max, - this.min, - this.target}); - - @override - final Iterable? seedArtists; - @override - final Iterable? seedGenres; - @override - final Iterable? seedTracks; - @override - final int limit; - @override - final RecommendationSeeds? max; - @override - final RecommendationSeeds? min; - @override - final RecommendationSeeds? target; - - @override - String toString() { - return 'GeneratePlaylistProviderInput(seedArtists: $seedArtists, seedGenres: $seedGenres, seedTracks: $seedTracks, limit: $limit, max: $max, min: $min, target: $target)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$GeneratePlaylistProviderInputImpl && - const DeepCollectionEquality() - .equals(other.seedArtists, seedArtists) && - const DeepCollectionEquality() - .equals(other.seedGenres, seedGenres) && - const DeepCollectionEquality() - .equals(other.seedTracks, seedTracks) && - (identical(other.limit, limit) || other.limit == limit) && - (identical(other.max, max) || other.max == max) && - (identical(other.min, min) || other.min == min) && - (identical(other.target, target) || other.target == target)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - const DeepCollectionEquality().hash(seedArtists), - const DeepCollectionEquality().hash(seedGenres), - const DeepCollectionEquality().hash(seedTracks), - limit, - max, - min, - target); - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$GeneratePlaylistProviderInputImplCopyWith< - _$GeneratePlaylistProviderInputImpl> - get copyWith => __$$GeneratePlaylistProviderInputImplCopyWithImpl< - _$GeneratePlaylistProviderInputImpl>(this, _$identity); -} - -abstract class _GeneratePlaylistProviderInput - implements GeneratePlaylistProviderInput { - factory _GeneratePlaylistProviderInput( - {final Iterable? seedArtists, - final Iterable? seedGenres, - final Iterable? seedTracks, - required final int limit, - final RecommendationSeeds? max, - final RecommendationSeeds? min, - final RecommendationSeeds? target}) = _$GeneratePlaylistProviderInputImpl; - - @override - Iterable? get seedArtists; - @override - Iterable? get seedGenres; - @override - Iterable? get seedTracks; - @override - int get limit; - @override - RecommendationSeeds? get max; - @override - RecommendationSeeds? get min; - @override - RecommendationSeeds? get target; - - /// Create a copy of GeneratePlaylistProviderInput - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$GeneratePlaylistProviderInputImplCopyWith< - _$GeneratePlaylistProviderInputImpl> - get copyWith => throw _privateConstructorUsedError; -} - -RecommendationSeeds _$RecommendationSeedsFromJson(Map json) { - return _RecommendationSeeds.fromJson(json); -} - -/// @nodoc -mixin _$RecommendationSeeds { - num? get acousticness => throw _privateConstructorUsedError; - num? get danceability => throw _privateConstructorUsedError; - @JsonKey(name: "duration_ms") - num? get durationMs => throw _privateConstructorUsedError; - num? get energy => throw _privateConstructorUsedError; - num? get instrumentalness => throw _privateConstructorUsedError; - num? get key => throw _privateConstructorUsedError; - num? get liveness => throw _privateConstructorUsedError; - num? get loudness => throw _privateConstructorUsedError; - num? get mode => throw _privateConstructorUsedError; - num? get popularity => throw _privateConstructorUsedError; - num? get speechiness => throw _privateConstructorUsedError; - num? get tempo => throw _privateConstructorUsedError; - @JsonKey(name: "time_signature") - num? get timeSignature => throw _privateConstructorUsedError; - num? get valence => throw _privateConstructorUsedError; - - /// Serializes this RecommendationSeeds to a JSON map. - Map toJson() => throw _privateConstructorUsedError; - - /// Create a copy of RecommendationSeeds - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - $RecommendationSeedsCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $RecommendationSeedsCopyWith<$Res> { - factory $RecommendationSeedsCopyWith( - RecommendationSeeds value, $Res Function(RecommendationSeeds) then) = - _$RecommendationSeedsCopyWithImpl<$Res, RecommendationSeeds>; - @useResult - $Res call( - {num? acousticness, - num? danceability, - @JsonKey(name: "duration_ms") num? durationMs, - num? energy, - num? instrumentalness, - num? key, - num? liveness, - num? loudness, - num? mode, - num? popularity, - num? speechiness, - num? tempo, - @JsonKey(name: "time_signature") num? timeSignature, - num? valence}); -} - -/// @nodoc -class _$RecommendationSeedsCopyWithImpl<$Res, $Val extends RecommendationSeeds> - implements $RecommendationSeedsCopyWith<$Res> { - _$RecommendationSeedsCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - /// Create a copy of RecommendationSeeds - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? acousticness = freezed, - Object? danceability = freezed, - Object? durationMs = freezed, - Object? energy = freezed, - Object? instrumentalness = freezed, - Object? key = freezed, - Object? liveness = freezed, - Object? loudness = freezed, - Object? mode = freezed, - Object? popularity = freezed, - Object? speechiness = freezed, - Object? tempo = freezed, - Object? timeSignature = freezed, - Object? valence = freezed, - }) { - return _then(_value.copyWith( - acousticness: freezed == acousticness - ? _value.acousticness - : acousticness // ignore: cast_nullable_to_non_nullable - as num?, - danceability: freezed == danceability - ? _value.danceability - : danceability // ignore: cast_nullable_to_non_nullable - as num?, - durationMs: freezed == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as num?, - energy: freezed == energy - ? _value.energy - : energy // ignore: cast_nullable_to_non_nullable - as num?, - instrumentalness: freezed == instrumentalness - ? _value.instrumentalness - : instrumentalness // ignore: cast_nullable_to_non_nullable - as num?, - key: freezed == key - ? _value.key - : key // ignore: cast_nullable_to_non_nullable - as num?, - liveness: freezed == liveness - ? _value.liveness - : liveness // ignore: cast_nullable_to_non_nullable - as num?, - loudness: freezed == loudness - ? _value.loudness - : loudness // ignore: cast_nullable_to_non_nullable - as num?, - mode: freezed == mode - ? _value.mode - : mode // ignore: cast_nullable_to_non_nullable - as num?, - popularity: freezed == popularity - ? _value.popularity - : popularity // ignore: cast_nullable_to_non_nullable - as num?, - speechiness: freezed == speechiness - ? _value.speechiness - : speechiness // ignore: cast_nullable_to_non_nullable - as num?, - tempo: freezed == tempo - ? _value.tempo - : tempo // ignore: cast_nullable_to_non_nullable - as num?, - timeSignature: freezed == timeSignature - ? _value.timeSignature - : timeSignature // ignore: cast_nullable_to_non_nullable - as num?, - valence: freezed == valence - ? _value.valence - : valence // ignore: cast_nullable_to_non_nullable - as num?, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$RecommendationSeedsImplCopyWith<$Res> - implements $RecommendationSeedsCopyWith<$Res> { - factory _$$RecommendationSeedsImplCopyWith(_$RecommendationSeedsImpl value, - $Res Function(_$RecommendationSeedsImpl) then) = - __$$RecommendationSeedsImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {num? acousticness, - num? danceability, - @JsonKey(name: "duration_ms") num? durationMs, - num? energy, - num? instrumentalness, - num? key, - num? liveness, - num? loudness, - num? mode, - num? popularity, - num? speechiness, - num? tempo, - @JsonKey(name: "time_signature") num? timeSignature, - num? valence}); -} - -/// @nodoc -class __$$RecommendationSeedsImplCopyWithImpl<$Res> - extends _$RecommendationSeedsCopyWithImpl<$Res, _$RecommendationSeedsImpl> - implements _$$RecommendationSeedsImplCopyWith<$Res> { - __$$RecommendationSeedsImplCopyWithImpl(_$RecommendationSeedsImpl _value, - $Res Function(_$RecommendationSeedsImpl) _then) - : super(_value, _then); - - /// Create a copy of RecommendationSeeds - /// with the given fields replaced by the non-null parameter values. - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? acousticness = freezed, - Object? danceability = freezed, - Object? durationMs = freezed, - Object? energy = freezed, - Object? instrumentalness = freezed, - Object? key = freezed, - Object? liveness = freezed, - Object? loudness = freezed, - Object? mode = freezed, - Object? popularity = freezed, - Object? speechiness = freezed, - Object? tempo = freezed, - Object? timeSignature = freezed, - Object? valence = freezed, - }) { - return _then(_$RecommendationSeedsImpl( - acousticness: freezed == acousticness - ? _value.acousticness - : acousticness // ignore: cast_nullable_to_non_nullable - as num?, - danceability: freezed == danceability - ? _value.danceability - : danceability // ignore: cast_nullable_to_non_nullable - as num?, - durationMs: freezed == durationMs - ? _value.durationMs - : durationMs // ignore: cast_nullable_to_non_nullable - as num?, - energy: freezed == energy - ? _value.energy - : energy // ignore: cast_nullable_to_non_nullable - as num?, - instrumentalness: freezed == instrumentalness - ? _value.instrumentalness - : instrumentalness // ignore: cast_nullable_to_non_nullable - as num?, - key: freezed == key - ? _value.key - : key // ignore: cast_nullable_to_non_nullable - as num?, - liveness: freezed == liveness - ? _value.liveness - : liveness // ignore: cast_nullable_to_non_nullable - as num?, - loudness: freezed == loudness - ? _value.loudness - : loudness // ignore: cast_nullable_to_non_nullable - as num?, - mode: freezed == mode - ? _value.mode - : mode // ignore: cast_nullable_to_non_nullable - as num?, - popularity: freezed == popularity - ? _value.popularity - : popularity // ignore: cast_nullable_to_non_nullable - as num?, - speechiness: freezed == speechiness - ? _value.speechiness - : speechiness // ignore: cast_nullable_to_non_nullable - as num?, - tempo: freezed == tempo - ? _value.tempo - : tempo // ignore: cast_nullable_to_non_nullable - as num?, - timeSignature: freezed == timeSignature - ? _value.timeSignature - : timeSignature // ignore: cast_nullable_to_non_nullable - as num?, - valence: freezed == valence - ? _value.valence - : valence // ignore: cast_nullable_to_non_nullable - as num?, - )); - } -} - -/// @nodoc -@JsonSerializable() -class _$RecommendationSeedsImpl implements _RecommendationSeeds { - _$RecommendationSeedsImpl( - {this.acousticness, - this.danceability, - @JsonKey(name: "duration_ms") this.durationMs, - this.energy, - this.instrumentalness, - this.key, - this.liveness, - this.loudness, - this.mode, - this.popularity, - this.speechiness, - this.tempo, - @JsonKey(name: "time_signature") this.timeSignature, - this.valence}); - - factory _$RecommendationSeedsImpl.fromJson(Map json) => - _$$RecommendationSeedsImplFromJson(json); - - @override - final num? acousticness; - @override - final num? danceability; - @override - @JsonKey(name: "duration_ms") - final num? durationMs; - @override - final num? energy; - @override - final num? instrumentalness; - @override - final num? key; - @override - final num? liveness; - @override - final num? loudness; - @override - final num? mode; - @override - final num? popularity; - @override - final num? speechiness; - @override - final num? tempo; - @override - @JsonKey(name: "time_signature") - final num? timeSignature; - @override - final num? valence; - - @override - String toString() { - return 'RecommendationSeeds(acousticness: $acousticness, danceability: $danceability, durationMs: $durationMs, energy: $energy, instrumentalness: $instrumentalness, key: $key, liveness: $liveness, loudness: $loudness, mode: $mode, popularity: $popularity, speechiness: $speechiness, tempo: $tempo, timeSignature: $timeSignature, valence: $valence)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$RecommendationSeedsImpl && - (identical(other.acousticness, acousticness) || - other.acousticness == acousticness) && - (identical(other.danceability, danceability) || - other.danceability == danceability) && - (identical(other.durationMs, durationMs) || - other.durationMs == durationMs) && - (identical(other.energy, energy) || other.energy == energy) && - (identical(other.instrumentalness, instrumentalness) || - other.instrumentalness == instrumentalness) && - (identical(other.key, key) || other.key == key) && - (identical(other.liveness, liveness) || - other.liveness == liveness) && - (identical(other.loudness, loudness) || - other.loudness == loudness) && - (identical(other.mode, mode) || other.mode == mode) && - (identical(other.popularity, popularity) || - other.popularity == popularity) && - (identical(other.speechiness, speechiness) || - other.speechiness == speechiness) && - (identical(other.tempo, tempo) || other.tempo == tempo) && - (identical(other.timeSignature, timeSignature) || - other.timeSignature == timeSignature) && - (identical(other.valence, valence) || other.valence == valence)); - } - - @JsonKey(includeFromJson: false, includeToJson: false) - @override - int get hashCode => Object.hash( - runtimeType, - acousticness, - danceability, - durationMs, - energy, - instrumentalness, - key, - liveness, - loudness, - mode, - popularity, - speechiness, - tempo, - timeSignature, - valence); - - /// Create a copy of RecommendationSeeds - /// with the given fields replaced by the non-null parameter values. - @JsonKey(includeFromJson: false, includeToJson: false) - @override - @pragma('vm:prefer-inline') - _$$RecommendationSeedsImplCopyWith<_$RecommendationSeedsImpl> get copyWith => - __$$RecommendationSeedsImplCopyWithImpl<_$RecommendationSeedsImpl>( - this, _$identity); - - @override - Map toJson() { - return _$$RecommendationSeedsImplToJson( - this, - ); - } -} - -abstract class _RecommendationSeeds implements RecommendationSeeds { - factory _RecommendationSeeds( - {final num? acousticness, - final num? danceability, - @JsonKey(name: "duration_ms") final num? durationMs, - final num? energy, - final num? instrumentalness, - final num? key, - final num? liveness, - final num? loudness, - final num? mode, - final num? popularity, - final num? speechiness, - final num? tempo, - @JsonKey(name: "time_signature") final num? timeSignature, - final num? valence}) = _$RecommendationSeedsImpl; - - factory _RecommendationSeeds.fromJson(Map json) = - _$RecommendationSeedsImpl.fromJson; - - @override - num? get acousticness; - @override - num? get danceability; - @override - @JsonKey(name: "duration_ms") - num? get durationMs; - @override - num? get energy; - @override - num? get instrumentalness; - @override - num? get key; - @override - num? get liveness; - @override - num? get loudness; - @override - num? get mode; - @override - num? get popularity; - @override - num? get speechiness; - @override - num? get tempo; - @override - @JsonKey(name: "time_signature") - num? get timeSignature; - @override - num? get valence; - - /// Create a copy of RecommendationSeeds - /// with the given fields replaced by the non-null parameter values. - @override - @JsonKey(includeFromJson: false, includeToJson: false) - _$$RecommendationSeedsImplCopyWith<_$RecommendationSeedsImpl> get copyWith => - throw _privateConstructorUsedError; -} diff --git a/lib/models/spotify/recommendation_seeds.g.dart b/lib/models/spotify/recommendation_seeds.g.dart deleted file mode 100644 index accb2ed1..00000000 --- a/lib/models/spotify/recommendation_seeds.g.dart +++ /dev/null @@ -1,44 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'recommendation_seeds.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -_$RecommendationSeedsImpl _$$RecommendationSeedsImplFromJson(Map json) => - _$RecommendationSeedsImpl( - acousticness: json['acousticness'] as num?, - danceability: json['danceability'] as num?, - durationMs: json['duration_ms'] as num?, - energy: json['energy'] as num?, - instrumentalness: json['instrumentalness'] as num?, - key: json['key'] as num?, - liveness: json['liveness'] as num?, - loudness: json['loudness'] as num?, - mode: json['mode'] as num?, - popularity: json['popularity'] as num?, - speechiness: json['speechiness'] as num?, - tempo: json['tempo'] as num?, - timeSignature: json['time_signature'] as num?, - valence: json['valence'] as num?, - ); - -Map _$$RecommendationSeedsImplToJson( - _$RecommendationSeedsImpl instance) => - { - 'acousticness': instance.acousticness, - 'danceability': instance.danceability, - 'duration_ms': instance.durationMs, - 'energy': instance.energy, - 'instrumentalness': instance.instrumentalness, - 'key': instance.key, - 'liveness': instance.liveness, - 'loudness': instance.loudness, - 'mode': instance.mode, - 'popularity': instance.popularity, - 'speechiness': instance.speechiness, - 'tempo': instance.tempo, - 'time_signature': instance.timeSignature, - 'valence': instance.valence, - }; diff --git a/lib/models/spotify_friends.dart b/lib/models/spotify_friends.dart deleted file mode 100644 index b386fb81..00000000 --- a/lib/models/spotify_friends.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:json_annotation/json_annotation.dart'; - -part 'spotify_friends.g.dart'; - -@JsonSerializable(createToJson: false) -class SpotifyFriend { - final String uri; - final String name; - final String imageUrl; - - const SpotifyFriend({ - required this.uri, - required this.name, - required this.imageUrl, - }); - - factory SpotifyFriend.fromJson(Map json) => - _$SpotifyFriendFromJson(json); - - String get id => uri.split(":").last; -} - -@JsonSerializable(createToJson: false) -class SpotifyActivityArtist { - final String uri; - final String name; - - const SpotifyActivityArtist({required this.uri, required this.name}); - - factory SpotifyActivityArtist.fromJson(Map json) => - _$SpotifyActivityArtistFromJson(json); - - String get id => uri.split(":").last; -} - -@JsonSerializable(createToJson: false) -class SpotifyActivityAlbum { - final String uri; - final String name; - - const SpotifyActivityAlbum({required this.uri, required this.name}); - - factory SpotifyActivityAlbum.fromJson(Map json) => - _$SpotifyActivityAlbumFromJson(json); - - String get id => uri.split(":").last; -} - -@JsonSerializable(createToJson: false) -class SpotifyActivityContext { - final String uri; - final String name; - final num index; - - const SpotifyActivityContext({ - required this.uri, - required this.name, - required this.index, - }); - - factory SpotifyActivityContext.fromJson(Map json) => - _$SpotifyActivityContextFromJson(json); - - String get id => uri.split(":").last; - String get path => uri.split(":").skip(1).join("/"); -} - -@JsonSerializable(createToJson: false) -class SpotifyActivityTrack { - final String uri; - final String name; - final String imageUrl; - final SpotifyActivityArtist artist; - final SpotifyActivityAlbum album; - final SpotifyActivityContext context; - - const SpotifyActivityTrack({ - required this.uri, - required this.name, - required this.imageUrl, - required this.artist, - required this.album, - required this.context, - }); - - factory SpotifyActivityTrack.fromJson(Map json) => - _$SpotifyActivityTrackFromJson(json); - - String get id => uri.split(":").last; -} - -@JsonSerializable(createToJson: false) -class SpotifyFriendActivity { - SpotifyFriend user; - SpotifyActivityTrack track; - - SpotifyFriendActivity({required this.user, required this.track}); - - factory SpotifyFriendActivity.fromJson(Map json) => - _$SpotifyFriendActivityFromJson(json); -} - -@JsonSerializable(createToJson: false) -class SpotifyFriends { - List friends; - - SpotifyFriends({required this.friends}); - - factory SpotifyFriends.fromJson(Map json) => - _$SpotifyFriendsFromJson(json); -} diff --git a/lib/models/spotify_friends.g.dart b/lib/models/spotify_friends.g.dart deleted file mode 100644 index a1248429..00000000 --- a/lib/models/spotify_friends.g.dart +++ /dev/null @@ -1,60 +0,0 @@ -// GENERATED CODE - DO NOT MODIFY BY HAND - -part of 'spotify_friends.dart'; - -// ************************************************************************** -// JsonSerializableGenerator -// ************************************************************************** - -SpotifyFriend _$SpotifyFriendFromJson(Map json) => SpotifyFriend( - uri: json['uri'] as String, - name: json['name'] as String, - imageUrl: json['imageUrl'] as String, - ); - -SpotifyActivityArtist _$SpotifyActivityArtistFromJson(Map json) => - SpotifyActivityArtist( - uri: json['uri'] as String, - name: json['name'] as String, - ); - -SpotifyActivityAlbum _$SpotifyActivityAlbumFromJson(Map json) => - SpotifyActivityAlbum( - uri: json['uri'] as String, - name: json['name'] as String, - ); - -SpotifyActivityContext _$SpotifyActivityContextFromJson(Map json) => - SpotifyActivityContext( - uri: json['uri'] as String, - name: json['name'] as String, - index: json['index'] as num, - ); - -SpotifyActivityTrack _$SpotifyActivityTrackFromJson(Map json) => - SpotifyActivityTrack( - uri: json['uri'] as String, - name: json['name'] as String, - imageUrl: json['imageUrl'] as String, - artist: SpotifyActivityArtist.fromJson( - Map.from(json['artist'] as Map)), - album: SpotifyActivityAlbum.fromJson( - Map.from(json['album'] as Map)), - context: SpotifyActivityContext.fromJson( - Map.from(json['context'] as Map)), - ); - -SpotifyFriendActivity _$SpotifyFriendActivityFromJson(Map json) => - SpotifyFriendActivity( - user: SpotifyFriend.fromJson( - Map.from(json['user'] as Map)), - track: SpotifyActivityTrack.fromJson( - Map.from(json['track'] as Map)), - ); - -SpotifyFriends _$SpotifyFriendsFromJson(Map json) => SpotifyFriends( - friends: (json['friends'] as List) - .map((e) => SpotifyFriendActivity.fromJson( - Map.from(e as Map))) - .toList(), - ); diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index 0ea07f51..b4809aed 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -2,14 +2,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; import 'package:spotube/components/playbutton_view/playbutton_tile.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/track.dart'; import 'package:spotube/models/connect/connect.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; @@ -17,10 +14,9 @@ import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -extension FormattedAlbumType on AlbumType { +extension FormattedAlbumType on SpotubeAlbumType { String get formatted => name.replaceFirst(name[0], name[0].toUpperCase()); } @@ -53,9 +49,11 @@ class AlbumCard extends HookConsumerWidget { final updating = useState(false); - Future> fetchAllTrack() async { - // return ref.read(metadataPluginAlbumTracksProvider(album).notifier).fetchAll(); - return []; + Future> fetchAllTrack() async { + await ref.read(metadataPluginAlbumTracksProvider(album.id).future); + return ref + .read(metadataPluginAlbumTracksProvider(album.id).notifier) + .fetchAll(); } var imageUrl = album.images.asUrlString( @@ -87,13 +85,13 @@ class AlbumCard extends HookConsumerWidget { await remotePlayback.load( WebSocketLoadEventData.album( tracks: fetchedTracks, - // collection: album, + collection: album, ), ); } else { await playlistNotifier.load(fetchedTracks, autoPlay: true); playlistNotifier.addCollection(album.id); - // historyNotifier.addAlbums([album]); + historyNotifier.addAlbums([album]); } } finally { updating.value = false; @@ -112,7 +110,7 @@ class AlbumCard extends HookConsumerWidget { if (fetchedTracks.isEmpty) return; playlistNotifier.addTracks(fetchedTracks); playlistNotifier.addCollection(album.id); - // historyNotifier.addAlbums([album]); + historyNotifier.addAlbums([album]); if (context.mounted) { showToast( context: context, @@ -126,7 +124,7 @@ class AlbumCard extends HookConsumerWidget { child: Text(context.l10n.undo), onPressed: () { playlistNotifier - .removeTracks(fetchedTracks.map((e) => e.id!)); + .removeTracks(fetchedTracks.map((e) => e.id)); }, ), ), diff --git a/lib/modules/artist/artist_album_list.dart b/lib/modules/artist/artist_album_list.dart index c5c0defd..8d228905 100644 --- a/lib/modules/artist/artist_album_list.dart +++ b/lib/modules/artist/artist_album_list.dart @@ -4,7 +4,7 @@ import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_pl import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/artist/albums.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; class ArtistAlbumList extends HookConsumerWidget { final String artistId; diff --git a/lib/modules/home/sections/featured.dart b/lib/modules/home/sections/featured.dart index a339bd43..c65ebf89 100644 --- a/lib/modules/home/sections/featured.dart +++ b/lib/modules/home/sections/featured.dart @@ -3,44 +3,45 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +@Deprecated( + "Later a featured playlists API will be added for metadata plugins.") class HomeFeaturedSection extends HookConsumerWidget { const HomeFeaturedSection({super.key}); @override Widget build(BuildContext context, ref) { - final featuredPlaylists = ref.watch(featuredPlaylistsProvider); - final featuredPlaylistsNotifier = - ref.watch(featuredPlaylistsProvider.notifier); + return const SizedBox.shrink(); + // final featuredPlaylists = ref.watch(featuredPlaylistsProvider); + // final featuredPlaylistsNotifier = + // ref.watch(featuredPlaylistsProvider.notifier); - if (featuredPlaylists.hasError) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Undraw( - illustration: UndrawIllustration.fixingBugs, - height: 200 * context.theme.scaling, - color: context.theme.colorScheme.primary, - ), - Text(context.l10n.something_went_wrong).small().muted(), - const Gap(8), - ], - ); - } + // if (featuredPlaylists.hasError) { + // return Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // Undraw( + // illustration: UndrawIllustration.fixingBugs, + // height: 200 * context.theme.scaling, + // color: context.theme.colorScheme.primary, + // ), + // Text(context.l10n.something_went_wrong).small().muted(), + // const Gap(8), + // ], + // ); + // } - return Skeletonizer( - enabled: featuredPlaylists.isLoading, - child: HorizontalPlaybuttonCardView( - items: featuredPlaylists.asData?.value.items ?? [], - title: Text(context.l10n.featured), - isLoadingNextPage: featuredPlaylists.isLoadingNextPage, - hasNextPage: featuredPlaylists.asData?.value.hasMore ?? false, - onFetchMore: featuredPlaylistsNotifier.fetchMore, - ), - ); + // return Skeletonizer( + // enabled: featuredPlaylists.isLoading, + // child: HorizontalPlaybuttonCardView( + // items: featuredPlaylists.asData?.value.items ?? [], + // title: Text(context.l10n.featured), + // isLoadingNextPage: featuredPlaylists.isLoadingNextPage, + // hasNextPage: featuredPlaylists.asData?.value.hasMore ?? false, + // onFetchMore: featuredPlaylistsNotifier.fetchMore, + // ), + // ); } } diff --git a/lib/modules/home/sections/friends.dart b/lib/modules/home/sections/friends.dart deleted file mode 100644 index 5c9c2178..00000000 --- a/lib/modules/home/sections/friends.dart +++ /dev/null @@ -1,65 +0,0 @@ -import 'dart:ui'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotube/collections/fake.dart'; -import 'package:spotube/modules/home/sections/friends/friend_item.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -class HomePageFriendsSection extends HookConsumerWidget { - const HomePageFriendsSection({super.key}); - - @override - Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); - final friendsQuery = ref.watch(friendsProvider); - final friends = - friendsQuery.asData?.value.friends ?? FakeData.friends.friends; - - if (friendsQuery.isLoading || - friendsQuery.asData?.value.friends.isEmpty == true || - auth.asData?.value == null) { - return const SizedBox.shrink(); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - context.l10n.friends, - style: context.theme.typography.h4, - ), - ), - SizedBox( - height: 80 * context.theme.scaling, - width: double.infinity, - child: ScrollConfiguration( - behavior: ScrollConfiguration.of(context).copyWith( - dragDevices: PointerDeviceKind.values.toSet(), - scrollbars: false, - ), - child: Skeletonizer( - enabled: friendsQuery.isLoading, - child: ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 8), - scrollDirection: Axis.horizontal, - itemCount: friends.length, - separatorBuilder: (context, index) => const Gap(8), - itemBuilder: (context, index) { - return FriendItem(friend: friends[index]); - }, - ), - ), - ), - ), - ], - ); - } -} diff --git a/lib/modules/home/sections/friends/friend_item.dart b/lib/modules/home/sections/friends/friend_item.dart deleted file mode 100644 index 00617404..00000000 --- a/lib/modules/home/sections/friends/friend_item.dart +++ /dev/null @@ -1,117 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter/gestures.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/models/spotify_friends.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -class FriendItem extends HookConsumerWidget { - final SpotifyFriendActivity friend; - const FriendItem({ - super.key, - required this.friend, - }); - - @override - Widget build(BuildContext context, ref) { - final spotify = ref.watch(spotifyProvider); - - return Card( - padding: const EdgeInsets.all(8), - child: Row( - children: [ - Avatar( - initials: Avatar.getInitials(friend.user.name), - provider: UniversalImage.imageProvider( - friend.user.imageUrl, - ), - ), - const Gap(8), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - friend.user.name, - style: context.theme.typography.bold, - ), - RichText( - text: TextSpan( - style: context.theme.typography.normal.copyWith( - color: context.theme.colorScheme.foreground, - ), - children: [ - TextSpan( - text: friend.track.name, - recognizer: TapGestureRecognizer() - ..onTap = () { - context - .navigateTo(TrackRoute(trackId: friend.track.id)); - }, - ), - const TextSpan(text: " • "), - const WidgetSpan( - child: Icon( - SpotubeIcons.artist, - size: 12, - ), - ), - TextSpan( - text: " ${friend.track.artist.name}", - recognizer: TapGestureRecognizer() - ..onTap = () { - context.navigateTo( - ArtistRoute(artistId: friend.track.artist.id), - ); - }, - ), - const TextSpan(text: "\n"), - TextSpan( - text: friend.track.context.name, - recognizer: TapGestureRecognizer() - ..onTap = () async { - context.router.navigateNamed( - "/${friend.track.context.path}", - // extra: - // !friend.track.context.path.startsWith("album") - // ? null - // : await spotify.albums - // .get(friend.track.context.id), - ); - }, - ), - const TextSpan(text: " • "), - const WidgetSpan( - child: Icon( - SpotubeIcons.album, - size: 12, - ), - ), - TextSpan( - text: " ${friend.track.album.name}", - recognizer: TapGestureRecognizer() - ..onTap = () async { - final album = await spotify.invoke( - (api) => api.albums.get(friend.track.album.id), - ); - if (context.mounted) { - // context.navigateTo( - // AlbumRoute(id: album.id!, album: album), - // ); - } - }, - ), - ], - ), - ), - ], - ), - ], - ), - ); - } -} diff --git a/lib/modules/home/sections/genres/genre_card.dart b/lib/modules/home/sections/genres/genre_card.dart deleted file mode 100644 index 8133f0db..00000000 --- a/lib/modules/home/sections/genres/genre_card.dart +++ /dev/null @@ -1,116 +0,0 @@ -import 'dart:math'; -import 'dart:ui'; - -import 'package:auto_route/auto_route.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotify/spotify.dart' hide Offset; -import 'package:spotube/collections/fake.dart'; -import 'package:spotube/collections/gradients.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/modules/home/sections/genres/genre_card_playlist_card.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -final random = Random(); -final gradientState = StateProvider.family( - (ref, String id) => gradients[random.nextInt(gradients.length)], -); - -class GenreSectionCard extends HookConsumerWidget { - final Category category; - const GenreSectionCard({ - super.key, - required this.category, - }); - - @override - Widget build(BuildContext context, ref) { - final theme = Theme.of(context); - final playlists = category == FakeData.category - ? null - : ref.watch(categoryPlaylistsProvider(category.id!)); - final playlistsData = playlists?.asData?.value.items.take(8) ?? - List.generate(5, (index) => FakeData.playlistSimple); - - final randomGradient = ref.watch(gradientState(category.id!)); - - return Container( - margin: const EdgeInsets.symmetric(horizontal: 8), - decoration: BoxDecoration( - borderRadius: theme.borderRadiusXxl, - boxShadow: [ - BoxShadow( - color: theme.colorScheme.foreground, - offset: const Offset(0, 5), - blurRadius: 7, - spreadRadius: -5, - ), - ], - image: DecorationImage( - image: UniversalImage.imageProvider( - category.icons!.first.url!, - ), - fit: BoxFit.cover, - ), - ), - child: Container( - padding: const EdgeInsets.all(16), - decoration: BoxDecoration( - borderRadius: theme.borderRadiusXxl, - gradient: randomGradient - .withOpacity(theme.brightness == Brightness.dark ? 0.2 : 0.7), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 16, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - category.name!, - style: const TextStyle(color: Colors.white), - ).h3(), - Button.link( - onPressed: () { - context.navigateTo( - GenrePlaylistsRoute( - id: category.id!, - category: category, - ), - ); - }, - child: Text( - context.l10n.view_all, - style: const TextStyle(color: Colors.white), - ).muted(), - ), - ], - ), - if (playlists?.hasError != true) - Expanded( - child: Skeleton.ignore( - child: Skeletonizer( - enabled: playlists?.isLoading ?? false, - child: ListView.separated( - scrollDirection: Axis.horizontal, - itemCount: playlistsData.length, - separatorBuilder: (context, index) => const Gap(12), - itemBuilder: (context, index) { - final playlist = playlistsData.elementAt(index); - - return GenreSectionCardPlaylistCard(playlist: playlist); - }, - ), - ), - ), - ) - ], - ), - ), - ); - } -} diff --git a/lib/modules/home/sections/genres/genre_card_playlist_card.dart b/lib/modules/home/sections/genres/genre_card_playlist_card.dart deleted file mode 100644 index 7bae4503..00000000 --- a/lib/modules/home/sections/genres/genre_card_playlist_card.dart +++ /dev/null @@ -1,130 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; -import 'package:spotify/spotify.dart' hide Image; -import 'package:spotube/collections/env.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/extensions/string.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:stroke_text/stroke_text.dart'; - -class GenreSectionCardPlaylistCard extends HookConsumerWidget { - final PlaylistSimple playlist; - const GenreSectionCardPlaylistCard({ - super.key, - required this.playlist, - }); - - @override - Widget build(BuildContext context, ref) { - final theme = Theme.of(context); - - return Container( - width: 115 * theme.scaling, - decoration: BoxDecoration( - color: theme.colorScheme.background.withAlpha(75), - borderRadius: theme.borderRadiusMd, - ), - child: SurfaceBlur( - borderRadius: theme.borderRadiusMd, - surfaceBlur: theme.surfaceBlur, - child: Button( - style: ButtonVariance.secondary.copyWith( - padding: (context, states, value) => const EdgeInsets.all(8), - decoration: (context, states, value) { - final decoration = ButtonVariance.secondary - .decoration(context, states) as BoxDecoration; - - if (states.isNotEmpty) { - return decoration; - } - - return decoration.copyWith( - color: decoration.color?.withAlpha(180), - ); - }, - ), - onPressed: () { - // context.navigateTo( - // PlaylistRoute(id: playlist.id!, playlist: playlist), - // ); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 5, - children: [ - ClipRRect( - borderRadius: theme.borderRadiusSm, - child: playlist.owner?.displayName == "Spotify" && - Env.disableSpotifyImages - ? Consumer( - builder: (context, ref, _) { - final (:src, :color, :colorBlendMode, :placement) = - ref.watch(playlistImageProvider(playlist.id!)); - return SizedBox( - height: 100 * theme.scaling, - width: 100 * theme.scaling, - child: Stack( - children: [ - Positioned.fill( - child: Image.asset( - src, - color: color, - colorBlendMode: colorBlendMode, - fit: BoxFit.cover, - ), - ), - Positioned.fill( - top: placement == Alignment.topLeft - ? 10 - : null, - left: 10, - bottom: placement == Alignment.bottomLeft - ? 10 - : null, - child: StrokeText( - text: playlist.name!, - strokeColor: Colors.white, - strokeWidth: 3, - textColor: Colors.black, - textStyle: const TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ), - ); - }, - ) - : UniversalImage( - path: (playlist.images)!.asUrlString( - placeholder: ImagePlaceholder.collection, - index: 1, - ), - fit: BoxFit.cover, - height: 100 * theme.scaling, - width: 100 * theme.scaling, - ), - ), - Text( - playlist.name!, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ).semiBold().small(), - if (playlist.description != null) - Text( - playlist.description?.unescapeHtml().cleanHtml() ?? "", - maxLines: 2, - overflow: TextOverflow.ellipsis, - ).xSmall().muted(), - ], - ), - ), - ), - ); - } -} diff --git a/lib/modules/home/sections/genres/genres.dart b/lib/modules/home/sections/genres/genres.dart deleted file mode 100644 index c9f3f9b2..00000000 --- a/lib/modules/home/sections/genres/genres.dart +++ /dev/null @@ -1,139 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotube/collections/fake.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/modules/home/sections/genres/genre_card.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -class HomeGenresSection extends HookConsumerWidget { - const HomeGenresSection({super.key}); - - @override - Widget build(BuildContext context, ref) { - final theme = context.theme; - final mediaQuery = MediaQuery.sizeOf(context); - - final categoriesQuery = ref.watch(categoriesProvider); - final categories = useMemoized( - () => - categoriesQuery.asData?.value - .where((c) => (c.icons?.length ?? 0) > 0) - .take(6) - .toList() ?? - [ - FakeData.category, - ], - [categoriesQuery.asData?.value], - ); - final controller = useMemoized(() => CarouselController(), []); - - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 8), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.l10n.genres, - style: context.theme.typography.h4, - ), - Button.link( - onPressed: () { - context.navigateTo(const GenreRoute()); - }, - child: Text( - context.l10n.browse_all, - ).muted(), - ), - ], - ), - ), - const Gap(8), - Stack( - children: [ - SizedBox( - height: 280 * theme.scaling, - child: Carousel( - controller: controller, - transition: const CarouselTransition.sliding(gap: 24), - sizeConstraint: CarouselSizeConstraint.fixed( - mediaQuery.mdAndUp - ? mediaQuery.width * .6 - : mediaQuery.width * .95, - ), - itemCount: categories.length, - pauseOnHover: true, - direction: Axis.horizontal, - itemBuilder: (context, index) { - final category = categories[index]; - - return Skeletonizer( - enabled: categoriesQuery.isLoading, - child: GenreSectionCard(category: category), - ); - }, - ), - ), - Positioned( - left: 0, - child: Container( - height: 280 * theme.scaling, - width: (mediaQuery.mdAndUp ? 60 : 40) * theme.scaling, - alignment: Alignment.center, - child: IconButton.secondary( - shape: ButtonShape.circle, - size: mediaQuery.mdAndUp - ? const ButtonSize(1.3) - : ButtonSize.normal, - icon: const Icon(SpotubeIcons.angleLeft), - onPressed: () { - controller.animatePrevious( - const Duration(seconds: 1), - ); - }, - ), - ), - ), - Positioned( - right: 0, - child: Container( - height: 280 * theme.scaling, - width: (mediaQuery.mdAndUp ? 60 : 40) * theme.scaling, - alignment: Alignment.center, - child: IconButton.secondary( - shape: ButtonShape.circle, - size: mediaQuery.mdAndUp - ? const ButtonSize(1.3) - : ButtonSize.normal, - icon: const Icon(SpotubeIcons.angleRight), - onPressed: () { - controller.animateNext( - const Duration(seconds: 1), - ); - }, - ), - ), - ), - ], - ), - const Gap(8), - Center( - child: CarouselDotIndicator( - itemCount: categories.length, - controller: controller, - ), - ), - ], - ); - } -} diff --git a/lib/modules/home/sections/made_for_user.dart b/lib/modules/home/sections/made_for_user.dart deleted file mode 100644 index 4fd025d5..00000000 --- a/lib/modules/home/sections/made_for_user.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -class HomeMadeForUserSection extends HookConsumerWidget { - const HomeMadeForUserSection({super.key}); - - @override - Widget build(BuildContext context, ref) { - final madeForUser = ref.watch(viewProvider("made-for-x-hub")); - - return SliverList.builder( - itemCount: madeForUser.asData?.value["content"]?["items"]?.length ?? 0, - itemBuilder: (context, index) { - final item = madeForUser.asData?.value["content"]?["items"]?[index]; - final playlists = item["content"]?["items"] - ?.where((itemL2) => itemL2["type"] == "playlist") - .map((itemL2) => PlaylistSimple.fromJson(itemL2)) - .toList() - .cast() ?? - []; - if (playlists.isEmpty) return const SizedBox.shrink(); - return HorizontalPlaybuttonCardView( - items: playlists, - title: Text(item["name"] ?? ""), - hasNextPage: false, - isLoadingNextPage: false, - onFetchMore: () {}, - ); - }, - ); - } -} diff --git a/lib/modules/home/sections/new_releases.dart b/lib/modules/home/sections/new_releases.dart index 2ebbbee0..31072954 100644 --- a/lib/modules/home/sections/new_releases.dart +++ b/lib/modules/home/sections/new_releases.dart @@ -1,10 +1,11 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/album/releases.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; class HomeNewReleasesSection extends HookConsumerWidget { const HomeNewReleasesSection({super.key}); @@ -13,10 +14,9 @@ class HomeNewReleasesSection extends HookConsumerWidget { Widget build(BuildContext context, ref) { final auth = ref.watch(authenticationProvider); - final newReleases = ref.watch(albumReleasesProvider); - final newReleasesNotifier = ref.read(albumReleasesProvider.notifier); - - final albums = ref.watch(userArtistAlbumReleasesProvider); + final newReleases = ref.watch(metadataPluginAlbumReleasesProvider); + final newReleasesNotifier = + ref.read(metadataPluginAlbumReleasesProvider.notifier); if (auth.asData?.value == null || newReleases.isLoading || @@ -24,8 +24,8 @@ class HomeNewReleasesSection extends HookConsumerWidget { return const SizedBox.shrink(); } - return HorizontalPlaybuttonCardView( - items: albums, + return HorizontalPlaybuttonCardView( + items: newReleases.asData?.value.items ?? [], title: Text(context.l10n.new_releases), isLoadingNextPage: newReleases.isLoadingNextPage, hasNextPage: newReleases.asData?.value.hasMore ?? false, diff --git a/lib/modules/library/local_folder/local_folder_item.dart b/lib/modules/library/local_folder/local_folder_item.dart index 78f1aa14..12dcdbbe 100644 --- a/lib/modules/library/local_folder/local_folder_item.dart +++ b/lib/modules/library/local_folder/local_folder_item.dart @@ -11,8 +11,8 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/string.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -100,7 +100,7 @@ class LocalFolderItem extends HookConsumerWidget { itemBuilder: (context, index) { final track = tracks[index]; return UniversalImage( - path: (track.album?.images).asUrlString( + path: track.album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), fit: BoxFit.cover, diff --git a/lib/modules/library/playlist_generate/multi_select_field.dart b/lib/modules/library/playlist_generate/multi_select_field.dart deleted file mode 100644 index 00a09c95..00000000 --- a/lib/modules/library/playlist_generate/multi_select_field.dart +++ /dev/null @@ -1,272 +0,0 @@ -import 'package:collection/collection.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:fuzzywuzzy/fuzzywuzzy.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; - -class MultiSelectField extends HookWidget { - final List options; - final List selectedOptions; - - final Widget Function(T option, VoidCallback onSelect)? optionBuilder; - final Widget Function(T option)? selectedOptionBuilder; - final ValueChanged> onSelected; - - final Widget? dialogTitle; - - final Object Function(T option) getValueForOption; - - final Widget label; - - final String? helperText; - - final bool enabled; - - const MultiSelectField({ - super.key, - required this.options, - required this.selectedOptions, - required this.getValueForOption, - required this.label, - this.optionBuilder, - this.selectedOptionBuilder, - required this.onSelected, - this.dialogTitle, - this.helperText, - this.enabled = true, - }); - - Widget defaultSelectedOptionBuilder(T option) { - return Chip( - label: Text(option.toString()), - onDeleted: () { - onSelected( - selectedOptions.where((e) => e != getValueForOption(option)).toList(), - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - final theme = Theme.of(context); - - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - MaterialButton( - elevation: 0, - focusElevation: 0, - hoverElevation: 0, - disabledElevation: 0, - highlightElevation: 0, - padding: const EdgeInsets.symmetric(vertical: 22), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(15), - side: BorderSide( - color: enabled - ? theme.colorScheme.onSurface - : theme.colorScheme.onSurface.withValues(alpha: 0.1), - ), - ), - mouseCursor: WidgetStateMouseCursor.textable, - onPressed: !enabled - ? null - : () async { - final selected = await showDialog>( - context: context, - builder: (context) { - return _MultiSelectDialog( - dialogTitle: dialogTitle, - options: options, - getValueForOption: getValueForOption, - optionBuilder: optionBuilder, - initialSelection: selectedOptions, - helperText: helperText, - ); - }, - ); - if (selected != null) { - onSelected(selected); - } - }, - child: Container( - alignment: Alignment.centerLeft, - margin: const EdgeInsets.symmetric(horizontal: 10), - child: DefaultTextStyle( - style: theme.textTheme.titleMedium!, - child: label, - ), - ), - ), - if (helperText != null) - Padding( - padding: const EdgeInsets.all(8.0), - child: Text( - helperText!, - style: Theme.of(context).textTheme.bodySmall, - ), - ), - Wrap( - children: [ - ...selectedOptions.map( - (option) => Padding( - padding: const EdgeInsets.all(4.0), - child: (selectedOptionBuilder ?? - defaultSelectedOptionBuilder)(option), - ), - ), - ], - ) - ], - ); - } -} - -class _MultiSelectDialog extends HookWidget { - final Widget? dialogTitle; - final List options; - final Widget Function(T option, VoidCallback onSelect)? optionBuilder; - final Object Function(T option) getValueForOption; - final List initialSelection; - final String? helperText; - - const _MultiSelectDialog({ - super.key, - required this.dialogTitle, - required this.options, - required this.getValueForOption, - this.optionBuilder, - this.initialSelection = const [], - this.helperText, - }); - - @override - Widget build(BuildContext context) { - final mediaQuery = MediaQuery.of(context); - final selected = useState(initialSelection.map(getValueForOption)); - - final searchController = useTextEditingController(); - - // creates render update - useValueListenable(searchController); - - final filteredOptions = useMemoized( - () { - if (searchController.text.isEmpty) { - return options; - } - - return options - .map((e) => ( - weightedRatio( - getValueForOption(e).toString(), searchController.text), - e - )) - .sorted((a, b) => b.$1.compareTo(a.$1)) - .where((e) => e.$1 > 50) - .map((e) => e.$2) - .toList(); - }, - [searchController.text, options, getValueForOption], - ); - - Widget defaultOptionBuilder(T option, VoidCallback onSelect) { - final isSelected = selected.value.contains(getValueForOption(option)); - return ChoiceChip( - label: Text("${!isSelected ? " " : ""}${option.toString()}"), - selected: isSelected, - side: BorderSide.none, - onSelected: (_) { - onSelect(); - }, - ); - } - - return AlertDialog( - scrollable: true, - title: dialogTitle ?? Text(context.l10n.select), - contentPadding: mediaQuery.mdAndUp ? null : const EdgeInsets.all(16), - insetPadding: const EdgeInsets.all(16), - actions: [ - OutlinedButton( - onPressed: () { - Navigator.pop(context); - }, - child: Text(context.l10n.cancel), - ), - ElevatedButton( - onPressed: () { - Navigator.pop( - context, - options - .where( - (option) => - selected.value.contains(getValueForOption(option)), - ) - .toList(), - ); - }, - child: Text(context.l10n.done), - ), - ], - content: SizedBox( - height: mediaQuery.size.height * 0.5, - width: 400, - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - TextField( - autofocus: true, - controller: searchController, - decoration: InputDecoration( - hintText: context.l10n.search, - prefixIcon: const Icon(SpotubeIcons.search), - border: OutlineInputBorder( - borderRadius: BorderRadius.circular(15), - ), - ), - ), - const SizedBox(height: 10), - Expanded( - child: SingleChildScrollView( - child: Wrap( - spacing: 5, - runSpacing: 5, - children: [ - ...filteredOptions.map( - (option) => Padding( - padding: const EdgeInsets.all(4.0), - child: (optionBuilder ?? defaultOptionBuilder)( - option, - () { - final value = getValueForOption(option); - if (selected.value.contains(value)) { - selected.value = selected.value - .where((e) => e != value) - .toList(); - } else { - selected.value = [...selected.value, value]; - } - }, - ), - ), - ), - ], - ), - ), - ), - if (helperText != null) - Text( - helperText!, - style: Theme.of(context).textTheme.labelMedium, - ), - ], - ), - ), - ); - } -} diff --git a/lib/modules/library/playlist_generate/recommendation_attribute_dials.dart b/lib/modules/library/playlist_generate/recommendation_attribute_dials.dart deleted file mode 100644 index 564bfb55..00000000 --- a/lib/modules/library/playlist_generate/recommendation_attribute_dials.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart'; - -typedef RecommendationAttribute = ({double min, double target, double max}); - -RecommendationAttribute lowValues(double base) => - (min: 1 * base, target: 0.3 * base, max: 0.3 * base); -RecommendationAttribute moderateValues(double base) => - (min: 0.5 * base, target: 1 * base, max: 0.5 * base); -RecommendationAttribute highValues(double base) => - (min: 0.3 * base, target: 0.3 * base, max: 1 * base); - -class RecommendationAttributeDials extends HookWidget { - final Widget title; - final RecommendationAttribute values; - final ValueChanged onChanged; - final double base; - - const RecommendationAttributeDials({ - super.key, - required this.values, - required this.onChanged, - required this.title, - this.base = 1, - }); - - @override - Widget build(BuildContext context) { - final labelStyle = Theme.of(context).typography.small.copyWith( - fontWeight: FontWeight.w500, - ); - - final minSlider = Row( - spacing: 5, - children: [ - Text(context.l10n.min, style: labelStyle), - Expanded( - child: Slider( - value: SliderValue.single(values.min / base), - min: 0, - max: 1, - onChanged: (value) => onChanged(( - min: value.value * base, - target: values.target, - max: values.max, - )), - ), - ), - ], - ); - - final targetSlider = Row( - spacing: 5, - children: [ - Text(context.l10n.target, style: labelStyle), - Expanded( - child: Slider( - value: SliderValue.single(values.target / base), - min: 0, - max: 1, - onChanged: (value) => onChanged(( - min: values.min, - target: value.value * base, - max: values.max, - )), - ), - ), - ], - ); - - final maxSlider = Row( - spacing: 5, - children: [ - Text(context.l10n.max, style: labelStyle), - Expanded( - child: Slider( - value: SliderValue.single(values.max / base), - min: 0, - max: 1, - onChanged: (value) => onChanged(( - min: values.min, - target: values.target, - max: value.value * base, - )), - ), - ), - ], - ); - - void onSelected(int index) { - RecommendationAttribute newValues = zeroValues; - switch (index) { - case 0: - newValues = lowValues(base); - break; - case 1: - newValues = moderateValues(base); - break; - case 2: - newValues = highValues(base); - break; - } - - if (newValues == values) { - onChanged(zeroValues); - } else { - onChanged(newValues); - } - } - - return LayoutBuilder(builder: (context, constrain) { - return Accordion( - items: [ - AccordionItem( - trigger: AccordionTrigger( - child: SizedBox( - width: double.infinity, - child: Basic( - title: title.semiBold(), - trailing: Row( - spacing: 5, - children: [ - Toggle( - value: values == lowValues(base), - onChanged: (value) => onSelected(0), - style: - const ButtonStyle.outline(size: ButtonSize.small), - child: Text(context.l10n.low), - ), - Toggle( - value: values == moderateValues(base), - onChanged: (value) => onSelected(1), - style: - const ButtonStyle.outline(size: ButtonSize.small), - child: Text(context.l10n.moderate), - ), - Toggle( - value: values == highValues(base), - onChanged: (value) => onSelected(2), - style: - const ButtonStyle.outline(size: ButtonSize.small), - child: Text(context.l10n.high), - ), - ], - ), - ), - ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - if (constrain.mdAndUp) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const SizedBox(width: 16), - Expanded(child: minSlider), - Expanded(child: targetSlider), - Expanded(child: maxSlider), - ], - ) - else - Padding( - padding: const EdgeInsets.only(left: 16), - child: Column( - children: [ - minSlider, - targetSlider, - maxSlider, - ], - ), - ), - ], - ), - ), - ], - ); - }); - } -} diff --git a/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart b/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart deleted file mode 100644 index b616b080..00000000 --- a/lib/modules/library/playlist_generate/recommendation_attribute_fields.dart +++ /dev/null @@ -1,182 +0,0 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; -import 'package:spotube/modules/library/playlist_generate/recommendation_attribute_dials.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart'; - -class RecommendationAttributeFields extends HookWidget { - final Widget title; - final RecommendationAttribute values; - final ValueChanged onChanged; - final Map? presets; - - const RecommendationAttributeFields({ - super.key, - required this.values, - required this.onChanged, - required this.title, - this.presets, - }); - - @override - Widget build(BuildContext context) { - final minController = - useShadcnTextEditingController(text: values.min.toString()); - final targetController = - useShadcnTextEditingController(text: values.target.toString()); - final maxController = - useShadcnTextEditingController(text: values.max.toString()); - - useEffect(() { - listener() { - onChanged(( - min: double.tryParse(minController.text) ?? 0, - target: double.tryParse(targetController.text) ?? 0, - max: double.tryParse(maxController.text) ?? 0, - )); - } - - minController.addListener(listener); - targetController.addListener(listener); - maxController.addListener(listener); - - return () { - minController.removeListener(listener); - targetController.removeListener(listener); - maxController.removeListener(listener); - }; - }, [values]); - - final minField = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 5, - children: [ - Text(context.l10n.min).semiBold(), - NumberInput( - controller: minController, - allowDecimals: false, - ), - ], - ); - - final targetField = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 5, - children: [ - Text(context.l10n.target).semiBold(), - NumberInput( - controller: targetController, - allowDecimals: false, - ), - ], - ); - - final maxField = Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 5, - children: [ - Text(context.l10n.max).semiBold(), - NumberInput( - controller: maxController, - allowDecimals: false, - ), - ], - ); - - void onSelected(int index) { - RecommendationAttribute newValues = presets!.values.elementAt(index); - if (newValues == values) { - onChanged(zeroValues); - minController.text = zeroValues.min.toString(); - targetController.text = zeroValues.target.toString(); - maxController.text = zeroValues.max.toString(); - } else { - onChanged(newValues); - minController.text = newValues.min.toString(); - targetController.text = newValues.target.toString(); - maxController.text = newValues.max.toString(); - } - } - - return LayoutBuilder(builder: (context, constraints) { - return Accordion( - items: [ - AccordionItem( - trigger: AccordionTrigger( - child: SizedBox( - width: double.infinity, - child: Basic( - title: title.semiBold(), - trailing: presets == null - ? const SizedBox.shrink() - : Padding( - padding: const EdgeInsets.symmetric(vertical: 8.0), - child: Row( - spacing: 5, - children: [ - for (final presetEntry in presets?.entries - .toList() ?? - >[]) - Toggle( - value: presetEntry.value == values, - style: const ButtonStyle.outline( - size: ButtonSize.small, - ), - onChanged: (value) { - onSelected( - presets!.entries.toList().indexWhere( - (s) => s.key == presetEntry.key), - ); - }, - child: Text(presetEntry.key), - ), - ], - ), - ), - ), - ), - ), - content: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const SizedBox(height: 8), - if (constraints.mdAndUp) - Row( - mainAxisAlignment: MainAxisAlignment.spaceEvenly, - children: [ - const SizedBox(width: 16), - Expanded(child: minField), - const SizedBox(width: 16), - Expanded(child: targetField), - const SizedBox(width: 16), - Expanded(child: maxField), - const SizedBox(width: 16), - ], - ) - else - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: Column( - children: [ - minField, - const SizedBox(height: 16), - targetField, - const SizedBox(height: 16), - maxField, - ], - ), - ), - const SizedBox(height: 8), - ], - ), - ), - ], - ); - }); - } -} diff --git a/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart b/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart deleted file mode 100644 index 812d9367..00000000 --- a/lib/modules/library/playlist_generate/seeds_multi_autocomplete.dart +++ /dev/null @@ -1,165 +0,0 @@ -import 'dart:async'; -import 'dart:math'; - -import 'package:flutter/material.dart' show Autocomplete; -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; - -enum SelectedItemDisplayType { - wrap, - list, -} - -class SeedsMultiAutocomplete extends HookWidget { - final ValueNotifier> seeds; - - final FutureOr> Function(TextEditingValue textEditingValue) - fetchSeeds; - final Widget Function(T option, ValueChanged onSelected) - autocompleteOptionBuilder; - final Widget Function(T option) selectedSeedBuilder; - final String Function(T option) displayStringForOption; - - final bool enabled; - - final SelectedItemDisplayType selectedItemDisplayType; - final Widget? placeholder; - final Widget? leading; - final Widget? trailing; - final Widget? label; - - const SeedsMultiAutocomplete({ - super.key, - required this.seeds, - required this.fetchSeeds, - required this.autocompleteOptionBuilder, - required this.displayStringForOption, - required this.selectedSeedBuilder, - this.enabled = true, - this.selectedItemDisplayType = SelectedItemDisplayType.wrap, - this.placeholder, - this.leading, - this.trailing, - this.label, - }); - - @override - Widget build(BuildContext context) { - useValueListenable(seeds); - final theme = Theme.of(context); - final mediaQuery = MediaQuery.of(context); - final seedController = useShadcnTextEditingController(); - - final containerKey = useRef(GlobalKey()); - - final box = - containerKey.value.currentContext?.findRenderObject() as RenderBox?; - final position = box?.localToGlobal(Offset.zero); //this is global position - final containerYPos = position?.dy ?? 0; //th - final containerHeight = box?.size.height ?? 0; - - final listHeight = mediaQuery.size.height - - (containerYPos + containerHeight) - - // bottom player bar height - (mediaQuery.mdAndUp ? 80 : 0); - - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - if (label != null) ...[ - label!.semiBold(), - const Gap(8), - ], - LayoutBuilder(builder: (context, constrains) { - return Container( - key: containerKey.value, - child: Autocomplete( - optionsBuilder: (textEditingValue) async { - if (textEditingValue.text.isEmpty) return []; - return fetchSeeds(textEditingValue); - }, - onSelected: (value) { - seeds.value = [...seeds.value, value]; - seedController.clear(); - }, - optionsViewBuilder: (context, onSelected, options) { - return Align( - alignment: Alignment.topLeft, - child: Container( - constraints: BoxConstraints( - maxWidth: constrains.maxWidth, - ), - height: max(listHeight, 0), - child: Card( - child: ListView.builder( - shrinkWrap: true, - itemCount: options.length, - itemBuilder: (context, index) { - final option = options.elementAt(index); - return autocompleteOptionBuilder(option, onSelected); - }, - ), - ), - ), - ); - }, - displayStringForOption: displayStringForOption, - fieldViewBuilder: ( - context, - textEditingController, - focusNode, - onFieldSubmitted, - ) { - return TextField( - controller: seedController, - onChanged: (value) => textEditingController.text = value, - focusNode: focusNode, - onSubmitted: (_) => onFieldSubmitted(), - enabled: enabled, - features: [ - if (leading != null) InputFeature.leading(leading!), - if (trailing != null) InputFeature.trailing(trailing!), - ], - placeholder: placeholder, - ); - }, - ), - ); - }), - const SizedBox(height: 8), - switch (selectedItemDisplayType) { - SelectedItemDisplayType.wrap => Wrap( - spacing: 4, - runSpacing: 4, - children: seeds.value.map(selectedSeedBuilder).toList(), - ), - SelectedItemDisplayType.list => AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - child: seeds.value.isEmpty - ? const SizedBox.shrink() - : Card( - child: Column( - children: [ - for (final seed in seeds.value) ...[ - selectedSeedBuilder(seed), - if (seeds.value.length > 1 && - seed != seeds.value.last) - Divider( - color: theme.colorScheme.secondary, - height: 1, - indent: 12, - endIndent: 12, - ), - ], - ], - ), - ), - ), - }, - ], - ); - } -} diff --git a/lib/modules/library/playlist_generate/simple_track_tile.dart b/lib/modules/library/playlist_generate/simple_track_tile.dart deleted file mode 100644 index afa723f3..00000000 --- a/lib/modules/library/playlist_generate/simple_track_tile.dart +++ /dev/null @@ -1,45 +0,0 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/spotube_icons.dart'; - -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/image.dart'; - -class SimpleTrackTile extends HookWidget { - final Track track; - final VoidCallback? onDelete; - const SimpleTrackTile({ - super.key, - required this.track, - this.onDelete, - }); - - @override - Widget build(BuildContext context) { - return ButtonTile( - leading: ClipRRect( - borderRadius: BorderRadius.circular(8), - child: UniversalImage( - path: (track.album?.images).asUrlString( - placeholder: ImagePlaceholder.artist, - ), - height: 40, - width: 40, - ), - ), - title: Text(track.name!), - trailing: onDelete == null - ? null - : IconButton.ghost( - icon: const Icon(SpotubeIcons.close), - onPressed: onDelete, - ), - subtitle: Text( - track.artists?.map((e) => e.name).join(", ") ?? track.album?.name ?? "", - ), - style: ButtonVariance.ghost, - ); - } -} diff --git a/lib/modules/library/user_downloads/download_item.dart b/lib/modules/library/user_downloads/download_item.dart index 2c0a96a5..2dcfc28f 100644 --- a/lib/modules/library/user_downloads/download_item.dart +++ b/lib/modules/library/user_downloads/download_item.dart @@ -2,20 +2,19 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/services/download_manager/download_status.dart'; import 'package:spotube/services/sourced_track/sourced_track.dart'; class DownloadItem extends HookConsumerWidget { - final Track track; + final SpotubeFullTrackObject track; const DownloadItem({ super.key, required this.track, @@ -29,7 +28,7 @@ class DownloadItem extends HookConsumerWidget { useEffect(() { if (track is! SourcedTrack) return null; - final notifier = downloadManager.getStatusNotifier(track as SourcedTrack); + final notifier = downloadManager.getStatusNotifier(track); taskStatus.value = notifier?.value; @@ -56,18 +55,18 @@ class DownloadItem extends HookConsumerWidget { child: UniversalImage( height: 40, width: 40, - path: (track.album?.images).asUrlString( + path: track.album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), ), ), ), - title: Text(track.name ?? ''), + title: Text(track.name), subtitle: ArtistLink( - artists: track.artists ?? [], + artists: track.artists, mainAxisAlignment: WrapAlignment.start, onOverflowArtistClick: () { - context.navigateTo(TrackRoute(trackId: track.id!)); + context.navigateTo(TrackRoute(trackId: track.id)); }, ), trailing: isQueryingSourceInfo @@ -75,8 +74,7 @@ class DownloadItem extends HookConsumerWidget { : switch (taskStatus.value!) { DownloadStatus.downloading => HookBuilder(builder: (context) { final taskProgress = useListenable(useMemoized( - () => downloadManager - .getProgressNotifier(track as SourcedTrack), + () => downloadManager.getProgressNotifier(track), [track], )); return Row( @@ -88,13 +86,13 @@ class DownloadItem extends HookConsumerWidget { IconButton.ghost( icon: const Icon(SpotubeIcons.pause), onPressed: () { - downloadManager.pause(track as SourcedTrack); + downloadManager.pause(track); }), const SizedBox(width: 10), IconButton.ghost( icon: const Icon(SpotubeIcons.close), onPressed: () { - downloadManager.cancel(track as SourcedTrack); + downloadManager.cancel(track); }), ], ); @@ -105,13 +103,13 @@ class DownloadItem extends HookConsumerWidget { IconButton.ghost( icon: const Icon(SpotubeIcons.play), onPressed: () { - downloadManager.resume(track as SourcedTrack); + downloadManager.resume(track); }), const SizedBox(width: 10), IconButton.ghost( icon: const Icon(SpotubeIcons.close), onPressed: () { - downloadManager.cancel(track as SourcedTrack); + downloadManager.cancel(track); }) ], ), @@ -127,7 +125,7 @@ class DownloadItem extends HookConsumerWidget { IconButton.ghost( icon: const Icon(SpotubeIcons.refresh), onPressed: () { - downloadManager.retry(track as SourcedTrack); + downloadManager.retry(track); }, ), ], @@ -138,7 +136,7 @@ class DownloadItem extends HookConsumerWidget { DownloadStatus.queued => IconButton.ghost( icon: const Icon(SpotubeIcons.close), onPressed: () { - downloadManager.removeFromQueue(track as SourcedTrack); + downloadManager.removeFromQueue(track); }), }, ); diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 28cd5835..8984455b 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -9,6 +9,7 @@ import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/framework/app_pop_scope.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/player/player_actions.dart'; import 'package:spotube/modules/player/player_controls.dart'; import 'package:spotube/modules/player/volume_slider.dart'; @@ -16,11 +17,8 @@ import 'package:spotube/components/dialogs/track_details_dialog.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -47,8 +45,8 @@ class PlayerView extends HookConsumerWidget { final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider); final currentActiveTrack = ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); - final currentTrack = sourcedCurrentTrack ?? currentActiveTrack; - final isLocalTrack = currentTrack is LocalTrack; + final currentActiveTrackSource = sourcedCurrentTrack.asData?.value?.source; + final isLocalTrack = currentActiveTrack is SpotubeLocalTrackObject; final mediaQuery = MediaQuery.sizeOf(context); final shouldHide = useState(true); @@ -71,10 +69,10 @@ class PlayerView extends HookConsumerWidget { }, [mediaQuery.lgAndUp]); String albumArt = useMemoized( - () => (currentTrack?.album?.images).asUrlString( + () => (currentActiveTrack?.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, ), - [currentTrack?.album?.images], + [currentActiveTrack?.album.images], ); useEffect(() { @@ -115,7 +113,7 @@ class PlayerView extends HookConsumerWidget { ) ], trailing: [ - if (currentTrack is YoutubeSourcedTrack) + if (currentActiveTrackSource is YoutubeSourcedTrack) TextButton( leading: Assets.logos.songlinkTransparent.image( width: 20, @@ -123,31 +121,34 @@ class PlayerView extends HookConsumerWidget { color: theme.colorScheme.foreground, ), onPressed: () { - final url = "https://song.link/s/${currentTrack.id}"; + final url = + "https://song.link/s/${currentActiveTrack?.id}"; launchUrlString(url); }, child: Text(context.l10n.song_link), ), - Tooltip( - tooltip: TooltipContainer( - child: Text(context.l10n.details), - ).call, - child: IconButton.ghost( - icon: const Icon(SpotubeIcons.info, size: 18), - onPressed: currentTrack == null - ? null - : () { - showDialog( - context: context, - builder: (context) { - return TrackDetailsDialog( - track: currentTrack, - ); - }); - }, - ), - ) + if (!isLocalTrack) + Tooltip( + tooltip: TooltipContainer( + child: Text(context.l10n.details), + ).call, + child: IconButton.ghost( + icon: const Icon(SpotubeIcons.info, size: 18), + onPressed: currentActiveTrackSource == null + ? null + : () { + showDialog( + context: context, + builder: (context) { + return TrackDetailsDialog( + track: currentActiveTrack + as SpotubeFullTrackObject, + ); + }); + }, + ), + ) ], ), ), @@ -190,7 +191,7 @@ class PlayerView extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AutoSizeText( - currentTrack?.name ?? context.l10n.not_playing, + currentActiveTrack?.name ?? context.l10n.not_playing, style: const TextStyle(fontSize: 22), maxFontSize: 22, maxLines: 1, @@ -198,13 +199,13 @@ class PlayerView extends HookConsumerWidget { ), if (isLocalTrack) Text( - currentTrack.artists?.asString() ?? "", + currentActiveTrack.artists.asString(), style: theme.typography.normal .copyWith(fontWeight: FontWeight.bold), ) else ArtistLink( - artists: currentTrack?.artists ?? [], + artists: currentActiveTrack?.artists ?? [], textStyle: theme.typography.normal .copyWith(fontWeight: FontWeight.bold), onRouteChange: (route) { @@ -212,7 +213,9 @@ class PlayerView extends HookConsumerWidget { context.router.navigateNamed(route); }, onOverflowArtistClick: () => context.navigateTo( - TrackRoute(trackId: currentTrack!.id!), + TrackRoute( + trackId: currentActiveTrack!.id, + ), ), ), ], diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index 53023a10..ee10e82a 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -8,14 +8,13 @@ import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/modules/player/sibling_tracks_sheet.dart'; import 'package:spotube/components/adaptive/adaptive_pop_sheet_list.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -38,12 +37,13 @@ class PlayerActions extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final playlist = ref.watch(audioPlayerProvider); - final isLocalTrack = playlist.activeTrack is LocalTrack; + final isLocalTrack = playlist.activeTrack is SpotubeLocalTrackObject; ref.watch(downloadManagerProvider); final downloader = ref.watch(downloadManagerProvider.notifier); final isInQueue = useMemoized(() { - if (playlist.activeTrack == null) return false; - return downloader.isActive(playlist.activeTrack!); + if (playlist.activeTrack is! SpotubeFullTrackObject) return false; + return downloader + .isActive(playlist.activeTrack! as SpotubeFullTrackObject); }, [ playlist.activeTrack, downloader, @@ -58,9 +58,9 @@ class PlayerActions extends HookConsumerWidget { return localTracks.any( (element) => element.name == playlist.activeTrack?.name && - element.album?.name == playlist.activeTrack?.album?.name && + element.album?.name == playlist.activeTrack?.album.name && element.artists?.asString() == - playlist.activeTrack?.artists?.asString(), + playlist.activeTrack?.artists.asString(), ) == true; }, [localTracks, playlist.activeTrack]); @@ -168,7 +168,8 @@ class PlayerActions extends HookConsumerWidget { isDownloaded ? SpotubeIcons.done : SpotubeIcons.download, ), onPressed: playlist.activeTrack != null - ? () => downloader.addToQueue(playlist.activeTrack!) + ? () => downloader.addToQueue( + playlist.activeTrack! as SpotubeFullTrackObject) : null, ), ), diff --git a/lib/modules/player/player_queue.dart b/lib/modules/player/player_queue.dart index 06d7e3c7..c9d5626f 100644 --- a/lib/modules/player/player_queue.dart +++ b/lib/modules/player/player_queue.dart @@ -7,16 +7,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/fallbacks/not_found.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/audio_player/state.dart'; @@ -24,7 +23,7 @@ class PlayerQueue extends HookConsumerWidget { final bool floating; final AudioPlayerState playlist; - final Future Function(Track track) onJump; + final Future Function(SpotubeTrackObject track) onJump; final Future Function(String trackId) onRemove; final Future Function(int oldIndex, int newIndex) onReorder; final Future Function() onStop; @@ -68,7 +67,7 @@ class PlayerQueue extends HookConsumerWidget { return tracks .map((e) => ( weightedRatio( - '${e.name!} - ${e.artists?.asString() ?? ""}', + '${e.name} - ${e.artists.asString()}', searchText.value, ), e @@ -161,7 +160,8 @@ class PlayerQueue extends HookConsumerWidget { const SizedBox(width: 10), Tooltip( tooltip: TooltipContainer( - child: Text(context.l10n.clear_all)).call, + child: Text(context.l10n.clear_all)) + .call, child: IconButton.outline( icon: const Icon(SpotubeIcons.playlistRemove), onPressed: () { @@ -244,7 +244,7 @@ class PlayerQueue extends HookConsumerWidget { icon: const Icon(SpotubeIcons.angleDown), onPressed: () { controller.scrollToIndex( - playlist.playlist.index, + playlist.currentIndex, preferPosition: AutoScrollPosition.middle, ); }, diff --git a/lib/modules/player/player_track_details.dart b/lib/modules/player/player_track_details.dart index 2e38bf37..995ed4ab 100644 --- a/lib/modules/player/player_track_details.dart +++ b/lib/modules/player/player_track_details.dart @@ -2,21 +2,19 @@ import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/links/link_text.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; class PlayerTrackDetails extends HookConsumerWidget { final Color? color; - final Track? track; + final SpotubeTrackObject? track; const PlayerTrackDetails({super.key, this.color, this.track}); @override @@ -37,7 +35,7 @@ class PlayerTrackDetails extends HookConsumerWidget { child: ClipRRect( borderRadius: BorderRadius.circular(4), child: UniversalImage( - path: (track?.album?.images) + path: (track?.album.images) .asUrlString(placeholder: ImagePlaceholder.albumArt), placeholder: Assets.albumPlaceholder.path, ), @@ -59,7 +57,7 @@ class PlayerTrackDetails extends HookConsumerWidget { ), ), Text( - playback.activeTrack?.artists?.asString() ?? "", + playback.activeTrack?.artists.asString() ?? "", overflow: TextOverflow.ellipsis, style: theme.typography.small.copyWith(color: color), ) @@ -84,7 +82,7 @@ class PlayerTrackDetails extends HookConsumerWidget { context.router.navigateNamed(route); }, onOverflowArtistClick: () => - context.navigateTo(TrackRoute(trackId: track!.id!)), + context.navigateTo(TrackRoute(trackId: track!.id)), ) ], ), diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index 472acf1b..fee0c46a 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -10,30 +10,27 @@ import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; import 'package:spotube/hooks/utils/use_debounce.dart'; import 'package:spotube/models/database/database.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/youtube_engine/youtube_engine.dart'; -import 'package:spotube/services/sourced_track/models/source_info.dart'; import 'package:spotube/services/sourced_track/models/video_info.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; -import 'package:spotube/services/sourced_track/sources/invidious.dart'; import 'package:spotube/services/sourced_track/sources/jiosaavn.dart'; -import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; import 'package:spotube/utils/service_utils.dart'; final sourceInfoToIconMap = { - YoutubeSourceInfo: const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)), - JioSaavnSourceInfo: Container( + AudioSource.youtube: + const Icon(SpotubeIcons.youtube, color: Color(0xFFFF0000)), + AudioSource.jiosaavn: Container( height: 30, width: 30, decoration: BoxDecoration( @@ -44,8 +41,8 @@ final sourceInfoToIconMap = { ), ), ), - PipedSourceInfo: const Icon(SpotubeIcons.piped), - InvidiousSourceInfo: Container( + AudioSource.piped: const Icon(SpotubeIcons.piped), + AudioSource.invidious: Container( height: 18, width: 18, decoration: BoxDecoration( @@ -68,25 +65,25 @@ class SiblingTracksSheet extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); - final playlist = ref.watch(audioPlayerProvider); final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); final preferences = ref.watch(userPreferencesProvider); final youtubeEngine = ref.watch(youtubeEngineProvider); final isSearching = useState(false); final searchMode = useState(preferences.searchMode); - final activeTrackNotifier = ref.watch(activeTrackSourcesProvider.notifier); - final activeTrack = - ref.watch(activeTrackSourcesProvider) ?? playlist.activeTrack; + final activeTrackSources = ref.watch(activeTrackSourcesProvider); + final activeTrackNotifier = activeTrackSources.asData?.value?.notifier; + final activeTrack = activeTrackSources.asData?.value?.track; + final activeTrackSource = activeTrackSources.asData?.value?.source; final title = ServiceUtils.getTitle( activeTrack?.name ?? "", - artists: activeTrack?.artists?.map((e) => e.name!).toList() ?? [], + artists: activeTrack?.artists.map((e) => e.name).toList() ?? [], onlyCleanArtist: true, ).trim(); final defaultSearchTerm = - "$title - ${activeTrack?.artists?.asString() ?? ""}"; + "$title - ${activeTrack?.artists.asString() ?? ""}"; final searchController = useShadcnTextEditingController( text: defaultSearchTerm, ); @@ -99,7 +96,7 @@ class SiblingTracksSheet extends HookConsumerWidget { final searchRequest = useMemoized(() async { if (searchTerm.trim().isEmpty) { - return []; + return []; } if (preferences.audioSource == AudioSource.jiosaavn) { final resultsJioSaavn = @@ -110,7 +107,7 @@ class SiblingTracksSheet extends HookConsumerWidget { return siblingType.info; })); - final activeSourceInfo = (activeTrack! as SourcedTrack).sourceInfo; + final activeSourceInfo = activeTrackSource as TrackSourceInfo; return results ..removeWhere((element) => element.id == activeSourceInfo.id) @@ -130,7 +127,7 @@ class SiblingTracksSheet extends HookConsumerWidget { return siblingType.info; }), ); - final activeSourceInfo = (activeTrack! as SourcedTrack).sourceInfo; + final activeSourceInfo = activeTrackSource as TrackSourceInfo; return searchResults ..removeWhere((element) => element.id == activeSourceInfo.id) ..insert( @@ -142,6 +139,7 @@ class SiblingTracksSheet extends HookConsumerWidget { searchTerm, searchMode.value, activeTrack, + activeTrackSource, preferences.audioSource, youtubeEngine, ]); @@ -149,25 +147,25 @@ class SiblingTracksSheet extends HookConsumerWidget { final siblings = useMemoized( () => !isFetchingActiveTrack ? [ - (activeTrack as SourcedTrack).sourceInfo, - ...activeTrack.siblings, + if (activeTrackSource != null) activeTrackSource.info, + ...?activeTrackSource?.siblings, ] - : [], - [activeTrack, isFetchingActiveTrack], + : [], + [activeTrackSource, isFetchingActiveTrack], ); final previousActiveTrack = usePrevious(activeTrack); useEffect(() { /// Populate sibling when active track changes if (previousActiveTrack?.id == activeTrack?.id) return; - if (activeTrack is SourcedTrack && activeTrack.siblings.isEmpty) { - activeTrackNotifier.populateSibling(); + if (activeTrackSource != null && activeTrackSource.siblings.isEmpty) { + activeTrackNotifier?.copyWithSibling(); } return null; }, [activeTrack, previousActiveTrack]); final itemBuilder = useCallback( - (SourceInfo sourceInfo) { + (TrackSourceInfo sourceInfo) { final icon = sourceInfoToIconMap[sourceInfo.runtimeType]; return ButtonTile( style: ButtonVariance.ghost, @@ -182,13 +180,14 @@ class SiblingTracksSheet extends HookConsumerWidget { height: 60, width: 60, ), - trailing: Text(sourceInfo.duration.toHumanReadableString()), + trailing: Text(Duration(milliseconds: sourceInfo.durationMs) + .toHumanReadableString()), subtitle: Row( children: [ if (icon != null) icon, Flexible( child: Text( - " • ${sourceInfo.artist}", + " • ${sourceInfo.artists}", maxLines: 1, overflow: TextOverflow.ellipsis, ), @@ -197,11 +196,11 @@ class SiblingTracksSheet extends HookConsumerWidget { ), enabled: !isFetchingActiveTrack, selected: !isFetchingActiveTrack && - sourceInfo.id == (activeTrack as SourcedTrack).sourceInfo.id, + sourceInfo.id == activeTrackSource?.info.id, onPressed: () { if (!isFetchingActiveTrack && - sourceInfo.id != (activeTrack as SourcedTrack).sourceInfo.id) { - activeTrackNotifier.swapSibling(sourceInfo); + sourceInfo.id != activeTrackSource?.info.id) { + activeTrackNotifier?.swapWithSibling(sourceInfo); if (MediaQuery.sizeOf(context).mdAndUp) { closeOverlay(context); } else { @@ -211,7 +210,7 @@ class SiblingTracksSheet extends HookConsumerWidget { }, ); }, - [activeTrack, siblings], + [activeTrackSource, activeTrackNotifier, siblings], ); final scale = context.theme.scaling; diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index 3d180e7c..36127792 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -2,8 +2,6 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; -import 'package:spotify/spotify.dart' hide Offset, Image; -import 'package:spotube/collections/env.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; @@ -15,9 +13,10 @@ import 'package:spotube/provider/audio_player/querying_track_info.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; -import 'package:stroke_text/stroke_text.dart'; class PlaylistCard extends HookConsumerWidget { final SpotubeSimplePlaylistObject playlist; @@ -48,26 +47,30 @@ class PlaylistCard extends HookConsumerWidget { ); final updating = useState(false); - final me = ref.watch(meProvider); + final me = ref.watch(metadataPluginUserProvider); - Future> fetchInitialTracks() async { + Future> fetchInitialTracks() async { if (playlist.id == 'user-liked-tracks') { - return await ref.read(likedTracksProvider.future); + final tracks = await ref.read(metadataPluginSavedTracksProvider.future); + return tracks.items; } - final result = await ref.read(playlistTracksProvider(playlist.id).future); + final result = await ref + .read(metadataPluginPlaylistTracksProvider(playlist.id).future); return result.items; } - Future> fetchAllTracks() async { + Future> fetchAllTracks() async { final initialTracks = await fetchInitialTracks(); if (playlist.id == 'user-liked-tracks') { return initialTracks; } - return ref.read(playlistTracksProvider(playlist.id).notifier).fetchAll(); + return ref + .read(metadataPluginPlaylistTracksProvider(playlist.id).notifier) + .fetchAll(); } void onTap() { @@ -94,14 +97,14 @@ class PlaylistCard extends HookConsumerWidget { final allTracks = await fetchAllTracks(); await remotePlayback.load( WebSocketLoadEventData.playlist( - tracks: allTracks, - // collection: playlist, + tracks: allTracks as List, + collection: playlist, ), ); } else { await playlistNotifier.load(fetchedInitialTracks, autoPlay: true); playlistNotifier.addCollection(playlist.id); - // historyNotifier.addPlaylists([playlist]); + historyNotifier.addPlaylists([playlist]); final allTracks = await fetchAllTracks(); @@ -126,7 +129,7 @@ class PlaylistCard extends HookConsumerWidget { playlistNotifier.addTracks(fetchedInitialTracks); playlistNotifier.addCollection(playlist.id); - // historyNotifier.addPlaylists([playlist]); + historyNotifier.addPlaylists([playlist]); if (context.mounted) { showToast( context: context, @@ -141,7 +144,7 @@ class PlaylistCard extends HookConsumerWidget { child: Text(context.l10n.undo), onPressed: () { playlistNotifier - .removeTracks(fetchedInitialTracks.map((e) => e.id!)); + .removeTracks(fetchedInitialTracks.map((e) => e.id)); }, ), ), @@ -159,52 +162,15 @@ class PlaylistCard extends HookConsumerWidget { ); final isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - final isOwner = - playlist.owner.id == me.asData?.value.id && me.asData?.value.id != null; - - final image = playlist.owner.name == "Spotify" && Env.disableSpotifyImages - ? Consumer( - builder: (context, ref, child) { - final (:color, :colorBlendMode, :src, :placement) = - ref.watch(playlistImageProvider(playlist.id)); - - return Stack( - children: [ - Positioned.fill( - child: Image.asset( - src, - color: color, - colorBlendMode: colorBlendMode, - fit: BoxFit.cover, - ), - ), - Positioned.fill( - top: placement == Alignment.topLeft ? 10 : null, - left: 10, - bottom: placement == Alignment.bottomLeft ? 10 : null, - child: StrokeText( - text: playlist.name, - strokeColor: Colors.white, - strokeWidth: 3, - textColor: Colors.black, - textStyle: const TextStyle( - fontSize: 16, - fontStyle: FontStyle.italic, - ), - ), - ), - ], - ); - }, - ) - : null; + final isOwner = playlist.owner.id == me.asData?.value?.id && + me.asData?.value?.id != null; if (_isTile) { return PlaybuttonTile( title: playlist.name, description: playlist.description, - image: image, - imageUrl: image == null ? imageUrl : null, + image: null, + imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, isOwner: isOwner, @@ -217,8 +183,8 @@ class PlaylistCard extends HookConsumerWidget { return PlaybuttonCard( title: playlist.name, description: playlist.description, - image: image, - imageUrl: image == null ? imageUrl : null, + image: null, + imageUrl: imageUrl, isPlaying: isPlaylistPlaying, isLoading: isLoading, isOwner: isOwner, diff --git a/lib/modules/playlist/playlist_create_dialog.dart b/lib/modules/playlist/playlist_create_dialog.dart index 3ee39583..dce17ed0 100644 --- a/lib/modules/playlist/playlist_create_dialog.dart +++ b/lib/modules/playlist/playlist_create_dialog.dart @@ -10,15 +10,15 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:image_picker/image_picker.dart'; import 'package:path/path.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/form/checkbox_form_field.dart'; import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; +import 'package:spotube/provider/metadata_plugin/playlist/playlist.dart'; class PlaylistCreateDialog extends HookConsumerWidget { /// Track ids to add to the playlist @@ -32,10 +32,11 @@ class PlaylistCreateDialog extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final userPlaylists = ref.watch(favoritePlaylistsProvider); - final playlist = ref.watch(playlistProvider(playlistId ?? "")); + final userPlaylists = ref.watch(metadataPluginSavedPlaylistsProvider); + final playlist = + ref.watch(metadataPluginPlaylistProvider(playlistId ?? "")); final playlistNotifier = - ref.watch(playlistProvider(playlistId ?? "").notifier); + ref.watch(metadataPluginPlaylistProvider(playlistId ?? "").notifier); final isSubmitting = useState(false); @@ -55,25 +56,54 @@ class PlaylistCreateDialog extends HookConsumerWidget { final l10n = context.l10n; final theme = Theme.of(context); + useEffect(() { + if (playlist.asData?.value != null) { + formKey.currentState?.patchValue({ + 'playlistName': playlist.asData!.value.name, + 'description': playlist.asData!.value.description, + 'public': playlist.asData!.value.public, + 'collaborative': playlist.asData!.value.collaborative, + }); + } + + return; + }, [playlist]); + final onError = useCallback((error) { - if (error is SpotifyError || error is SpotifyException) { - showToast( - context: context, - location: ToastLocation.topRight, - builder: (context, overlay) { - return SurfaceCard( - child: Basic( - title: Text( - l10n.error(error.message ?? l10n.epic_failure), - style: theme.typography.normal.copyWith( - color: theme.colorScheme.destructive, - ), + // if (error is SpotifyError || error is SpotifyException) { + // showToast( + // context: context, + // location: ToastLocation.topRight, + // builder: (context, overlay) { + // return SurfaceCard( + // child: Basic( + // title: Text( + // l10n.error(error.message ?? l10n.epic_failure), + // style: theme.typography.normal.copyWith( + // color: theme.colorScheme.destructive, + // ), + // ), + // ), + // ); + // }, + // ); + // } + showToast( + context: context, + location: ToastLocation.topRight, + builder: (context, overlay) { + return SurfaceCard( + child: Basic( + title: Text( + l10n.error(l10n.epic_failure), + style: theme.typography.normal.copyWith( + color: theme.colorScheme.destructive, ), ), - ); - }, - ); - } + ), + ); + }, + ); }, [l10n, theme]); Future onCreate() async { @@ -83,7 +113,7 @@ class PlaylistCreateDialog extends HookConsumerWidget { isSubmitting.value = true; final values = formKey.currentState!.value; - final PlaylistInput payload = ( + final payload = ( playlistName: values['playlistName'], collaborative: values['collaborative'], public: values['public'], @@ -96,9 +126,21 @@ class PlaylistCreateDialog extends HookConsumerWidget { ); if (isUpdatingPlaylist) { - await playlistNotifier.modify(payload, onError); + await playlistNotifier.modify( + name: payload.playlistName, + description: payload.description, + public: payload.public, + collaborative: payload.collaborative, + onError: onError, + ); } else { - await playlistNotifier.create(payload, onError); + await playlistNotifier.create( + name: payload.playlistName, + description: payload.description, + public: payload.public, + collaborative: payload.collaborative, + onError: onError, + ); } if (trackIds.isNotEmpty) { @@ -107,9 +149,12 @@ class PlaylistCreateDialog extends HookConsumerWidget { } finally { isSubmitting.value = false; if (context.mounted && - !ref.read(playlistProvider(playlistId ?? "")).hasError) { - context.router.maybePop( - await ref.read(playlistProvider(playlistId ?? "").future), + !ref + .read(metadataPluginPlaylistProvider(playlistId ?? "")) + .hasError) { + context.router.maybePop( + await ref + .read(metadataPluginPlaylistProvider(playlistId ?? "").future), ); } } @@ -144,8 +189,8 @@ class PlaylistCreateDialog extends HookConsumerWidget { initialValue: { 'playlistName': updatingPlaylist?.name, 'description': updatingPlaylist?.description, - 'public': updatingPlaylist?.public ?? false, - 'collaborative': updatingPlaylist?.collaborative ?? false, + 'public': playlist.asData?.value.public ?? false, + 'collaborative': playlist.asData?.value.collaborative ?? false, }, child: ListView( shrinkWrap: true, @@ -259,7 +304,7 @@ class PlaylistCreateDialog extends HookConsumerWidget { class PlaylistCreateDialogButton extends HookConsumerWidget { const PlaylistCreateDialogButton({super.key}); - showPlaylistDialog(BuildContext context, SpotifyApiWrapper spotify) { + showPlaylistDialog(BuildContext context) { showDialog( context: context, alignment: Alignment.center, @@ -271,12 +316,10 @@ class PlaylistCreateDialogButton extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final spotify = ref.watch(spotifyProvider); - return Button.secondary( leading: const Icon(SpotubeIcons.addFilled), child: Text(context.l10n.playlist), - onPressed: () => showPlaylistDialog(context, spotify), + onPressed: () => showPlaylistDialog(context), ); } } diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index c2e6369d..8af5d433 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -8,6 +8,7 @@ import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/player/player_actions.dart'; import 'package:spotube/modules/player/player_overlay.dart'; import 'package:spotube/modules/player/player_track_details.dart'; @@ -15,7 +16,6 @@ import 'package:spotube/modules/player/player_controls.dart'; import 'package:spotube/modules/player/volume_slider.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -35,13 +35,13 @@ class BottomPlayer extends HookConsumerWidget { final mediaQuery = MediaQuery.of(context); String albumArt = useMemoized( - () => playlist.activeTrack?.album?.images?.isNotEmpty == true - ? (playlist.activeTrack?.album?.images).asUrlString( - index: (playlist.activeTrack?.album?.images?.length ?? 1) - 1, + () => playlist.activeTrack?.album.images.isNotEmpty == true + ? (playlist.activeTrack?.album.images).asUrlString( + index: (playlist.activeTrack?.album.images.length ?? 1) - 1, placeholder: ImagePlaceholder.albumArt, ) : Assets.albumPlaceholder.path, - [playlist.activeTrack?.album?.images], + [playlist.activeTrack?.album.images], ); // returning an empty non spacious Container as the overlay will take @@ -76,7 +76,8 @@ class BottomPlayer extends HookConsumerWidget { extraActions: [ Tooltip( tooltip: - TooltipContainer(child: Text(context.l10n.mini_player)).call, + TooltipContainer(child: Text(context.l10n.mini_player)) + .call, child: IconButton( variance: ButtonVariance.ghost, icon: const Icon(SpotubeIcons.miniPlayer), diff --git a/lib/modules/root/sidebar/sidebar_footer.dart b/lib/modules/root/sidebar/sidebar_footer.dart index fb3edddd..f7168086 100644 --- a/lib/modules/root/sidebar/sidebar_footer.dart +++ b/lib/modules/root/sidebar/sidebar_footer.dart @@ -6,13 +6,13 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/connect/connect_device.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { const SidebarFooter({ @@ -25,11 +25,11 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { final router = AutoRouter.of(context, watch: true); final mediaQuery = MediaQuery.of(context); final downloadCount = ref.watch(downloadManagerProvider).$downloadCount; - final userSnapshot = ref.watch(meProvider); + final userSnapshot = ref.watch(metadataPluginUserProvider); final data = userSnapshot.asData?.value; final avatarImg = (data?.images).asUrlString( - index: (data?.images?.length ?? 1) - 1, + index: (data?.images.length ?? 1) - 1, placeholder: ImagePlaceholder.artist, ); @@ -102,14 +102,13 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { child: Row( children: [ Avatar( - initials: - Avatar.getInitials(data.displayName ?? "User"), + initials: Avatar.getInitials(data.name), provider: UniversalImage.imageProvider(avatarImg), ), const SizedBox(width: 10), Flexible( child: Text( - data.displayName ?? context.l10n.guest, + data.name, maxLines: 1, softWrap: false, overflow: TextOverflow.fade, diff --git a/lib/modules/stats/common/album_item.dart b/lib/modules/stats/common/album_item.dart index b69e1d15..2ac73b91 100644 --- a/lib/modules/stats/common/album_item.dart +++ b/lib/modules/stats/common/album_item.dart @@ -1,15 +1,14 @@ import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; +import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/links/artist_link.dart'; -import 'package:spotube/extensions/image.dart'; class StatsAlbumItem extends StatelessWidget { - final AlbumSimple album; + final SpotubeSimpleAlbumObject album; final Widget info; const StatsAlbumItem({super.key, required this.album, required this.info}); @@ -27,24 +26,24 @@ class StatsAlbumItem extends StatelessWidget { height: 40, ), ), - title: Text(album.name!), + title: Text(album.name), subtitle: Row( mainAxisSize: MainAxisSize.min, children: [ - Text("${album.albumType?.formatted} • "), - // Flexible( - // child: ArtistLink( - // artists: album.artists ?? [], - // mainAxisAlignment: WrapAlignment.start, - // onOverflowArtistClick: () => - // context.navigateTo(AlbumRoute(id: album.id!, album: album)), - // ), - // ), + Text("${album.albumType.formatted} • "), + Flexible( + child: ArtistLink( + artists: album.artists, + mainAxisAlignment: WrapAlignment.start, + onOverflowArtistClick: () => + context.navigateTo(AlbumRoute(id: album.id, album: album)), + ), + ), ], ), trailing: info, onPressed: () { - // context.navigateTo(AlbumRoute(id: album.id!, album: album)); + context.navigateTo(AlbumRoute(id: album.id, album: album)); }, ); } diff --git a/lib/modules/stats/common/artist_item.dart b/lib/modules/stats/common/artist_item.dart index 5eff9a9d..92d3b915 100644 --- a/lib/modules/stats/common/artist_item.dart +++ b/lib/modules/stats/common/artist_item.dart @@ -1,13 +1,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class StatsArtistItem extends StatelessWidget { - final Artist artist; + final SpotubeSimpleArtistObject artist; final Widget info; const StatsArtistItem({ super.key, @@ -19,9 +18,9 @@ class StatsArtistItem extends StatelessWidget { Widget build(BuildContext context) { return ButtonTile( style: ButtonVariance.ghost, - title: Text(artist.name!), + title: Text(artist.name), leading: Avatar( - initials: artist.name!.substring(0, 1), + initials: artist.name.substring(0, 1), provider: UniversalImage.imageProvider( (artist.images).asUrlString( placeholder: ImagePlaceholder.artist, @@ -30,7 +29,7 @@ class StatsArtistItem extends StatelessWidget { ), trailing: info, onPressed: () { - context.navigateTo(ArtistRoute(artistId: artist.id!)); + context.navigateTo(ArtistRoute(artistId: artist.id)); }, ); } diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index b1fdc920..8121f946 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -1,14 +1,11 @@ -import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/string.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class StatsPlaylistItem extends StatelessWidget { - final PlaylistSimple playlist; + final SpotubeSimplePlaylistObject playlist; final Widget info; const StatsPlaylistItem( {super.key, required this.playlist, required this.info}); @@ -27,9 +24,9 @@ class StatsPlaylistItem extends StatelessWidget { height: 40, ), ), - title: Text(playlist.name!), + title: Text(playlist.name), subtitle: Text( - playlist.description?.unescapeHtml() ?? '', + playlist.description.unescapeHtml(), maxLines: 1, overflow: TextOverflow.ellipsis, ), diff --git a/lib/modules/stats/common/track_item.dart b/lib/modules/stats/common/track_item.dart index ae2e22c6..eea3dd4b 100644 --- a/lib/modules/stats/common/track_item.dart +++ b/lib/modules/stats/common/track_item.dart @@ -1,14 +1,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; import 'package:spotube/components/ui/button_tile.dart'; -import 'package:spotube/extensions/image.dart'; +import 'package:spotube/models/metadata/metadata.dart'; class StatsTrackItem extends StatelessWidget { - final Track track; + final SpotubeTrackObject track; final Widget info; const StatsTrackItem({ super.key, @@ -23,24 +22,24 @@ class StatsTrackItem extends StatelessWidget { leading: ClipRRect( borderRadius: BorderRadius.circular(4), child: UniversalImage( - path: (track.album?.images).asUrlString( + path: (track.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, ), width: 40, height: 40, ), ), - title: Text(track.name!), + title: Text(track.name), subtitle: ArtistLink( - artists: track.artists!, + artists: track.artists, mainAxisAlignment: WrapAlignment.start, onOverflowArtistClick: () { - context.navigateTo(TrackRoute(trackId: track.id!)); + context.navigateTo(TrackRoute(trackId: track.id)); }, ), trailing: info, onPressed: () { - context.navigateTo(TrackRoute(trackId: track.id!)); + context.navigateTo(TrackRoute(trackId: track.id)); }, ); } diff --git a/lib/modules/stats/top/albums.dart b/lib/modules/stats/top/albums.dart index 09bf755c..e2a9042a 100644 --- a/lib/modules/stats/top/albums.dart +++ b/lib/modules/stats/top/albums.dart @@ -8,7 +8,7 @@ import 'package:spotube/modules/stats/common/album_item.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/albums.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class TopAlbums extends HookConsumerWidget { diff --git a/lib/modules/stats/top/artists.dart b/lib/modules/stats/top/artists.dart index cb2a152f..5a8dc441 100644 --- a/lib/modules/stats/top/artists.dart +++ b/lib/modules/stats/top/artists.dart @@ -9,7 +9,7 @@ import 'package:spotube/modules/stats/common/artist_item.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class TopArtists extends HookConsumerWidget { @@ -24,14 +24,8 @@ class TopArtists extends HookConsumerWidget { final topTracksNotifier = ref.watch(historyTopTracksProvider(historyDuration).notifier); - final artistsData = useMemoized( - () => topTracks.asData?.value.artists ?? [], - [topTracks.asData?.value], - ); - - for (final artist in artistsData) { - print("${artist.artist.name} has ${artist.artist.images?.length} images"); - } + final artistsData = + useMemoized(() => topTracksNotifier.artists, [topTracks.asData?.value]); return Skeletonizer.sliver( enabled: topTracks.isLoading && !topTracks.isLoadingNextPage, diff --git a/lib/modules/stats/top/tracks.dart b/lib/modules/stats/top/tracks.dart index c4015431..08c742c4 100644 --- a/lib/modules/stats/top/tracks.dart +++ b/lib/modules/stats/top/tracks.dart @@ -8,7 +8,7 @@ import 'package:spotube/modules/stats/common/track_item.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; class TopTracks extends HookConsumerWidget { diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index dd57b2e3..11d06658 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -8,7 +8,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:spotube/provider/metadata_plugin/tracks/album.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; @RoutePage() class AlbumPage extends HookConsumerWidget { diff --git a/lib/pages/artist/artist.dart b/lib/pages/artist/artist.dart index 1dce98e9..d6243f71 100644 --- a/lib/pages/artist/artist.dart +++ b/lib/pages/artist/artist.dart @@ -7,15 +7,17 @@ import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/modules/artist/artist_album_list.dart'; -import 'package:spotube/extensions/context.dart'; import 'package:spotube/pages/artist/section/footer.dart'; import 'package:spotube/pages/artist/section/header.dart'; // import 'package:spotube/pages/artist/section/related_artists.dart'; import 'package:spotube/pages/artist/section/top_tracks.dart'; +import 'package:spotube/provider/metadata_plugin/artist/albums.dart'; import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/provider/metadata_plugin/artist/top_tracks.dart'; +import 'package:spotube/provider/metadata_plugin/artist/wikipedia.dart'; +import 'package:spotube/provider/metadata_plugin/library/artists.dart'; @RoutePage() class ArtistPage extends HookConsumerWidget { @@ -30,7 +32,6 @@ class ArtistPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final scrollController = useScrollController(); - final theme = Theme.of(context); final artistQuery = ref.watch(metadataPluginArtistProvider(artistId)); @@ -46,14 +47,15 @@ class ArtistPage extends HookConsumerWidget { floatingHeader: true, child: material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(artistProvider(artistId)); - ref.invalidate(relatedArtistsProvider(artistId)); - ref.invalidate(artistAlbumsProvider(artistId)); - ref.invalidate(artistIsFollowingProvider(artistId)); - ref.invalidate(artistTopTracksProvider(artistId)); + ref.invalidate(metadataPluginArtistProvider(artistId)); + // ref.invalidate(relatedArtistsProvider(artistId)); + ref.invalidate(metadataPluginArtistAlbumsProvider(artistId)); + ref.invalidate(metadataPluginIsSavedArtistProvider(artistId)); + ref.invalidate(metadataPluginArtistTopTracksProvider(artistId)); if (artistQuery.hasValue) { ref.invalidate( - artistWikipediaSummaryProvider(artistQuery.asData!.value)); + artistWikipediaSummaryProvider(artistQuery.asData!.value), + ); } }, child: Builder(builder: (context) { diff --git a/lib/pages/artist/section/footer.dart b/lib/pages/artist/section/footer.dart index 247f8879..938fb6fc 100644 --- a/lib/pages/artist/section/footer.dart +++ b/lib/pages/artist/section/footer.dart @@ -5,8 +5,7 @@ import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - +import 'package:spotube/provider/metadata_plugin/artist/wikipedia.dart'; import 'package:url_launcher/url_launcher_string.dart'; class ArtistPageFooter extends ConsumerWidget { diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 6ca03a01..3a850668 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -13,7 +13,7 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:spotube/utils/primitive_utils.dart'; class ArtistPageHeader extends HookConsumerWidget { @@ -31,7 +31,7 @@ class ArtistPageHeader extends HookConsumerWidget { final auth = ref.watch(authenticationProvider); ref.watch(blacklistProvider); final blacklistNotifier = ref.watch(blacklistProvider.notifier); - final isBlackListed = /* blacklistNotifier.containsArtist(artist) */ false; + final isBlackListed = blacklistNotifier.containsArtist(artist.id); final image = artist.images.asUrlString( placeholder: ImagePlaceholder.artist, @@ -45,11 +45,10 @@ class ArtistPageHeader extends HookConsumerWidget { Consumer( builder: (context, ref, _) { final isFollowingQuery = ref.watch( - artistIsFollowingProvider(artist.id!), - ); - final followingArtistNotifier = ref.watch( - followedArtistsProvider.notifier, + metadataPluginIsSavedArtistProvider(artist.id), ); + final followingArtistNotifier = + ref.watch(metadataPluginSavedArtistsProvider.notifier); return switch (isFollowingQuery) { AsyncData(value: final following) => Builder( @@ -58,7 +57,7 @@ class ArtistPageHeader extends HookConsumerWidget { return Button.outline( onPressed: () async { await followingArtistNotifier - .removeArtists([artist.id!]); + .removeFavorite([artist]); }, child: Text(context.l10n.following), ); @@ -66,8 +65,7 @@ class ArtistPageHeader extends HookConsumerWidget { return Button.primary( onPressed: () async { - await followingArtistNotifier - .saveArtists([artist.id!]); + await followingArtistNotifier.addFavorite([artist]); }, child: Text(context.l10n.follow), ); @@ -96,12 +94,12 @@ class ArtistPageHeader extends HookConsumerWidget { : ButtonVariance.ghost, onPressed: () async { if (isBlackListed) { - await ref.read(blacklistProvider.notifier).remove(artist.id!); + await ref.read(blacklistProvider.notifier).remove(artist.id); } else { await ref.read(blacklistProvider.notifier).add( BlacklistTableCompanion.insert( - name: artist.name!, - elementId: artist.id!, + name: artist.name, + elementId: artist.id, elementType: BlacklistedType.artist, ), ); @@ -184,7 +182,7 @@ class ArtistPageHeader extends HookConsumerWidget { const Gap(10), Flexible( child: AutoSizeText( - artist.name!, + artist.name, style: constrains.smAndDown ? typography.h4 : typography.h3, diff --git a/lib/pages/artist/section/related_artists.dart b/lib/pages/artist/section/related_artists.dart index bc03fbe5..050c8a5c 100644 --- a/lib/pages/artist/section/related_artists.dart +++ b/lib/pages/artist/section/related_artists.dart @@ -1,7 +1,5 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/modules/artist/artist_card.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; @Deprecated("Related artists are no longer supported by Spotube") class ArtistPageRelatedArtists extends ConsumerWidget { @@ -13,38 +11,39 @@ class ArtistPageRelatedArtists extends ConsumerWidget { @override Widget build(BuildContext context, ref) { - final relatedArtists = ref.watch(relatedArtistsProvider(artistId)); + return const SizedBox.shrink(); + // final relatedArtists = ref.watch(relatedArtistsProvider(artistId)); - return switch (relatedArtists) { - AsyncData(value: final artists) => SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 8.0), - sliver: SliverGrid.builder( - itemCount: artists.length, - gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( - maxCrossAxisExtent: 200, - mainAxisExtent: 250, - mainAxisSpacing: 10, - crossAxisSpacing: 10, - childAspectRatio: 0.8, - ), - itemBuilder: (context, index) { - final artist = artists.elementAt(index); - return SizedBox( - width: 180, - // child: ArtistCard(artist), - ); - // return ArtistCard(artist); - }, - ), - ), - AsyncError(:final error) => SliverToBoxAdapter( - child: Center( - child: Text(error.toString()), - ), - ), - _ => const SliverToBoxAdapter( - child: Center(child: CircularProgressIndicator()), - ), - }; + // return switch (relatedArtists) { + // AsyncData(value: final artists) => SliverPadding( + // padding: const EdgeInsets.symmetric(horizontal: 8.0), + // sliver: SliverGrid.builder( + // itemCount: artists.length, + // gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent( + // maxCrossAxisExtent: 200, + // mainAxisExtent: 250, + // mainAxisSpacing: 10, + // crossAxisSpacing: 10, + // childAspectRatio: 0.8, + // ), + // itemBuilder: (context, index) { + // final artist = artists.elementAt(index); + // return SizedBox( + // width: 180, + // // child: ArtistCard(artist), + // ); + // // return ArtistCard(artist); + // }, + // ), + // ), + // AsyncError(:final error) => SliverToBoxAdapter( + // child: Center( + // child: Text(error.toString()), + // ), + // ), + // _ => const SliverToBoxAdapter( + // child: Center(child: CircularProgressIndicator()), + // ), + // }; } } diff --git a/lib/pages/artist/section/top_tracks.dart b/lib/pages/artist/section/top_tracks.dart index 72709751..9ec7314b 100644 --- a/lib/pages/artist/section/top_tracks.dart +++ b/lib/pages/artist/section/top_tracks.dart @@ -1,16 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/fake.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/artist/top_tracks.dart'; class ArtistPageTopTracks extends HookConsumerWidget { final String artistId; @@ -22,10 +22,11 @@ class ArtistPageTopTracks extends HookConsumerWidget { final playlist = ref.watch(audioPlayerProvider); final playlistNotifier = ref.watch(audioPlayerProvider.notifier); - final topTracksQuery = ref.watch(artistTopTracksProvider(artistId)); + final topTracksQuery = + ref.watch(metadataPluginArtistTopTracksProvider(artistId)); final isPlaylistPlaying = playlist.containsTracks( - topTracksQuery.asData?.value ?? [], + topTracksQuery.asData?.value.items ?? [], ); if (topTracksQuery.hasError) { @@ -36,10 +37,11 @@ class ArtistPageTopTracks extends HookConsumerWidget { ); } - final topTracks = topTracksQuery.asData?.value ?? + final topTracks = topTracksQuery.asData?.value.items ?? List.generate(10, (index) => FakeData.track); - void playPlaylist(List tracks, {Track? currentTrack}) async { + void playPlaylist(List tracks, + {SpotubeTrackObject? currentTrack}) async { currentTrack ??= tracks.first; final isRemoteDevice = await showSelectDeviceDialog(context, ref); @@ -61,7 +63,6 @@ class ArtistPageTopTracks extends HookConsumerWidget { ), ); } else if (isPlaylistPlaying && - currentTrack.id != null && currentTrack.id != remotePlaylist.activeTrack?.id) { final index = playlist.tracks .toList() @@ -76,7 +77,6 @@ class ArtistPageTopTracks extends HookConsumerWidget { autoPlay: true, ); } else if (isPlaylistPlaying && - currentTrack.id != null && currentTrack.id != playlist.activeTrack?.id) { await playlistNotifier.jumpToTrack(currentTrack); } diff --git a/lib/pages/connect/control/control.dart b/lib/pages/connect/control/control.dart index e28566fd..6abb11eb 100644 --- a/lib/pages/connect/control/control.dart +++ b/lib/pages/connect/control/control.dart @@ -8,6 +8,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/player/player_queue.dart'; import 'package:spotube/modules/player/volume_slider.dart'; import 'package:spotube/components/image/universal_image.dart'; @@ -17,7 +18,6 @@ import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/provider/connect/clients.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:media_kit/media_kit.dart' hide Track; @@ -120,7 +120,7 @@ class ConnectControlPage extends HookConsumerWidget { child: ClipRRect( borderRadius: BorderRadius.circular(20), child: UniversalImage( - path: (playlist.activeTrack?.album?.images) + path: (playlist.activeTrack?.album.images) .asUrlString( placeholder: ImagePlaceholder.albumArt, ), @@ -140,8 +140,7 @@ class ConnectControlPage extends HookConsumerWidget { onTap: () { if (playlist.activeTrack == null) return; context.navigateTo( - TrackRoute( - trackId: playlist.activeTrack!.id!), + TrackRoute(trackId: playlist.activeTrack!.id), ); }, ), @@ -152,7 +151,7 @@ class ConnectControlPage extends HookConsumerWidget { textStyle: typography.normal, mainAxisAlignment: WrapAlignment.start, onOverflowArtistClick: () => context.navigateTo( - TrackRoute(trackId: playlist.activeTrack!.id!), + TrackRoute(trackId: playlist.activeTrack!.id), ), ), ), diff --git a/lib/pages/getting_started/sections/region.dart b/lib/pages/getting_started/sections/region.dart index f657f9d9..b0f3051c 100644 --- a/lib/pages/getting_started/sections/region.dart +++ b/lib/pages/getting_started/sections/region.dart @@ -1,6 +1,5 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/language_codes.dart'; import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -14,7 +13,7 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { const GettingStartedPageLanguageRegionSection( {super.key, required this.onNext}); - bool filterMarkets(Market item, String query) { + bool filterMarkets(dynamic item, String query) { final market = spotifyMarkets .firstWhere((element) => element.$1 == item) .$2 @@ -64,7 +63,7 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { const Gap(8), SizedBox( width: double.infinity, - child: Select( + child: Select( value: preferences.market, onChanged: (value) { if (value == null) return; diff --git a/lib/pages/home/genres/genre_playlists.dart b/lib/pages/home/genres/genre_playlists.dart deleted file mode 100644 index bf36f834..00000000 --- a/lib/pages/home/genres/genre_playlists.dart +++ /dev/null @@ -1,145 +0,0 @@ -import 'package:flutter/material.dart' show CollapseMode, FlexibleSpaceBar; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; - -import 'package:spotify/spotify.dart' hide Offset; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/button/back_button.dart'; -import 'package:spotube/components/playbutton_view/playbutton_view.dart'; -import 'package:spotube/hooks/utils/use_custom_status_bar_color.dart'; -import 'package:spotube/modules/playlist/playlist_card.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:spotube/utils/platform.dart'; -import 'package:auto_route/auto_route.dart'; - -@RoutePage() -class GenrePlaylistsPage extends HookConsumerWidget { - static const name = "genre_playlists"; - - final Category category; - final String id; - const GenrePlaylistsPage({ - super.key, - @PathParam("categoryId") required this.id, - required this.category, - }); - - @override - Widget build(BuildContext context, ref) { - final mediaQuery = MediaQuery.of(context); - final playlists = ref.watch(categoryPlaylistsProvider(category.id!)); - final playlistsNotifier = - ref.read(categoryPlaylistsProvider(category.id!).notifier); - final scrollController = useScrollController(); - - useCustomStatusBarColor( - Colors.black, - context.watchRouter.topRoute.name == GenrePlaylistsRoute.name, - noSetBGColor: true, - automaticSystemUiAdjustment: false, - ); - - return SafeArea( - child: Scaffold( - headers: [ - if (kIsDesktop) - const TitleBar( - leading: [ - BackButton(), - ], - backgroundColor: Colors.transparent, - surfaceOpacity: 0, - surfaceBlur: 0, - ) - ], - floatingHeader: true, - child: DecoratedBox( - decoration: BoxDecoration( - image: DecorationImage( - image: UniversalImage.imageProvider(category.icons!.first.url!), - alignment: Alignment.topCenter, - fit: BoxFit.cover, - repeat: ImageRepeat.noRepeat, - matchTextDirection: true, - ), - ), - child: SurfaceCard( - borderRadius: BorderRadius.zero, - padding: EdgeInsets.zero, - child: CustomScrollView( - controller: scrollController, - slivers: [ - SliverSafeArea( - bottom: false, - sliver: SliverAppBar( - automaticallyImplyLeading: false, - leading: kIsMobile ? const BackButton() : null, - expandedHeight: mediaQuery.mdAndDown ? 200 : 150, - title: const Text(""), - backgroundColor: Colors.transparent, - flexibleSpace: FlexibleSpaceBar( - centerTitle: kIsDesktop, - title: Text( - category.name!, - style: context.theme.typography.h3.copyWith( - color: Colors.white, - letterSpacing: 3, - shadows: [ - Shadow( - offset: const Offset(-1.5, -1.5), - color: Colors.black.withAlpha(138), - ), - Shadow( - offset: const Offset(1.5, -1.5), - color: Colors.black.withAlpha(138), - ), - Shadow( - offset: const Offset(1.5, 1.5), - color: Colors.black.withAlpha(138), - ), - Shadow( - offset: const Offset(-1.5, 1.5), - color: Colors.black.withAlpha(138), - ), - ], - ), - ), - collapseMode: CollapseMode.parallax, - ), - ), - ), - const SliverGap(20), - // SliverSafeArea( - // top: false, - // sliver: SliverPadding( - // padding: EdgeInsets.symmetric( - // horizontal: mediaQuery.mdAndDown ? 12 : 24, - // ), - // sliver: PlaybuttonView( - // controller: scrollController, - // itemCount: playlists.asData?.value.items.length ?? 0, - // isLoading: playlists.isLoading, - // hasMore: playlists.asData?.value.hasMore == true, - // onRequestMore: playlistsNotifier.fetchMore, - // listItemBuilder: (context, index) => PlaylistCard.tile( - // playlists.asData!.value.items[index]), - // gridItemBuilder: (context, index) => - // PlaylistCard(playlists.asData!.value.items[index]), - // ), - // ), - // ), - const SliverGap(20), - ], - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/home/genres/genres.dart b/lib/pages/home/genres/genres.dart deleted file mode 100644 index 38d110db..00000000 --- a/lib/pages/home/genres/genres.dart +++ /dev/null @@ -1,106 +0,0 @@ -import 'dart:math'; - -import 'package:auto_size_text/auto_size_text.dart'; - -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; -import 'package:spotube/collections/gradients.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:auto_route/auto_route.dart'; - -@RoutePage() -class GenrePage extends HookConsumerWidget { - static const name = "genre"; - const GenrePage({super.key}); - - @override - Widget build(BuildContext context, ref) { - final scrollController = useScrollController(); - final categories = ref.watch(categoriesProvider); - - final mediaQuery = MediaQuery.of(context); - - return SafeArea( - child: Scaffold( - headers: [ - TitleBar( - title: Text(context.l10n.explore_genres), - ) - ], - child: GridView.builder( - padding: const EdgeInsets.all(12), - controller: scrollController, - gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( - childAspectRatio: 9 / 18, - maxCrossAxisExtent: mediaQuery.smAndDown ? 200 : 300, - mainAxisExtent: 200, - crossAxisSpacing: 12, - mainAxisSpacing: 12, - ), - itemCount: categories.asData!.value.length, - itemBuilder: (context, index) { - final category = categories.asData!.value[index]; - final gradient = gradients[Random().nextInt(gradients.length)]; - return CardImage( - onPressed: () { - context.navigateTo( - GenrePlaylistsRoute( - id: category.id!, - category: category, - ), - ); - }, - image: Stack( - children: [ - Container( - height: 300, - width: 250, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(8), - image: DecorationImage( - image: NetworkImage(category.icons!.first.url!), - fit: BoxFit.cover, - ), - gradient: gradient, - ), - ), - Positioned.fill( - bottom: 10, - child: Align( - alignment: Alignment.bottomCenter, - child: AutoSizeText( - category.name!, - style: context.theme.typography.h3.copyWith( - color: Colors.white, - shadows: [ - // stroke shadow - const Shadow( - color: Colors.black, - offset: Offset(1, 1), - blurRadius: 2, - ), - ], - ), - maxLines: 1, - textAlign: TextAlign.center, - maxFontSize: context.theme.typography.h3.fontSize!, - minFontSize: context.theme.typography.large.fontSize!, - ), - ), - ), - ], - ), - ); - }, - ), - ), - ); - } -} diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index 53bb4fb2..d766fd2a 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -13,9 +13,6 @@ import 'package:spotube/models/database/database.dart'; import 'package:spotube/modules/connect/connect_device.dart'; import 'package:spotube/modules/home/sections/featured.dart'; import 'package:spotube/modules/home/sections/sections.dart'; -import 'package:spotube/modules/home/sections/friends.dart'; -import 'package:spotube/modules/home/sections/genres/genres.dart'; -import 'package:spotube/modules/home/sections/made_for_user.dart'; import 'package:spotube/modules/home/sections/new_releases.dart'; import 'package:spotube/modules/home/sections/recent.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; @@ -76,18 +73,18 @@ class HomePage extends HookConsumerWidget { else if (kIsMacOS) const SliverGap(10), const SliverGap(10), - // SliverList.builder( - // itemCount: 5, - // itemBuilder: (context, index) { - // return switch (index) { - // 0 => const HomeGenresSection(), - // 1 => const HomeRecentlyPlayedSection(), - // 2 => const HomeFeaturedSection(), - // 3 => const HomePageFriendsSection(), - // _ => const HomeNewReleasesSection() - // }; - // }, - // ), + SliverList.builder( + itemCount: 3, + itemBuilder: (context, index) { + return switch (index) { + // 0 => const HomeGenresSection(), + 0 => const HomeRecentlyPlayedSection(), + 1 => const HomeFeaturedSection(), + // 3 => const HomePageFriendsSection(), + _ => const HomeNewReleasesSection() + }; + }, + ), const HomePageBrowseSection(), ], ), diff --git a/lib/pages/library/playlist_generate/playlist_generate.dart b/lib/pages/library/playlist_generate/playlist_generate.dart deleted file mode 100644 index f1eca306..00000000 --- a/lib/pages/library/playlist_generate/playlist_generate.dart +++ /dev/null @@ -1,717 +0,0 @@ -import 'package:collection/collection.dart'; - -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/collections/spotify_markets.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/button/back_button.dart'; -import 'package:spotube/components/ui/button_tile.dart'; - -import 'package:spotube/modules/library/playlist_generate/recommendation_attribute_dials.dart'; -import 'package:spotube/modules/library/playlist_generate/recommendation_attribute_fields.dart'; -import 'package:spotube/modules/library/playlist_generate/seeds_multi_autocomplete.dart'; -import 'package:spotube/modules/library/playlist_generate/simple_track_tile.dart'; -import 'package:spotube/components/image/universal_image.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/constrains.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/models/spotify/recommendation_seeds.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:auto_route/auto_route.dart'; - -const RecommendationAttribute zeroValues = (min: 0, target: 0, max: 0); - -@RoutePage() -class PlaylistGeneratorPage extends HookConsumerWidget { - static const name = "playlist_generator"; - - const PlaylistGeneratorPage({super.key}); - - @override - Widget build(BuildContext context, ref) { - final spotify = ref.watch(spotifyProvider); - - final theme = Theme.of(context); - final typography = theme.typography; - final preferences = ref.watch(userPreferencesProvider); - - final genresCollection = ref.watch(categoryGenresProvider); - - final limit = useValueNotifier(10); - final market = useValueNotifier(preferences.market); - - final genres = useState>([]); - final artists = useState>([]); - final tracks = useState>([]); - - final enabled = - genres.value.length + artists.value.length + tracks.value.length < 5; - - final leftSeedCount = - 5 - genres.value.length - artists.value.length - tracks.value.length; - - // Dial (int 0-1) attributes - final min = useState(RecommendationSeeds()); - final max = useState(RecommendationSeeds()); - final target = useState(RecommendationSeeds()); - - final artistAutoComplete = SeedsMultiAutocomplete( - seeds: artists, - enabled: enabled, - label: Text(context.l10n.artists), - placeholder: Text(context.l10n.select_up_to_count_type( - leftSeedCount, - context.l10n.artists, - )), - fetchSeeds: (textEditingValue) => spotify.invoke( - (api) => api.search - .get( - textEditingValue.text, - types: [SearchType.artist], - ) - .first(6) - .then( - (v) => List.castFrom( - v.expand((e) => e.items ?? []).toList(), - ) - .where( - (element) => - artists.value.none((artist) => element.id == artist.id), - ) - .toList(), - ), - ), - autocompleteOptionBuilder: (option, onSelected) => ButtonTile( - leading: Avatar( - initials: "O", - provider: UniversalImage.imageProvider( - option.images.asUrlString( - placeholder: ImagePlaceholder.artist, - ), - ), - ), - title: Text(option.name!), - subtitle: option.genres?.isNotEmpty != true - ? null - : Wrap( - spacing: 4, - runSpacing: 4, - children: option.genres!.mapIndexed( - (index, genre) { - return Chip( - style: ButtonVariance.secondary, - child: Text(genre), - ); - }, - ).toList(), - ), - onPressed: () => onSelected(option), - style: ButtonVariance.ghost, - ), - displayStringForOption: (option) => option.name!, - selectedSeedBuilder: (artist) => OutlineBadge( - leading: Avatar( - initials: artist.name!.substring(0, 1), - size: 30, - provider: UniversalImage.imageProvider( - artist.images.asUrlString( - placeholder: ImagePlaceholder.artist, - ), - ), - ), - trailing: IconButton.ghost( - icon: const Icon(SpotubeIcons.close), - onPressed: () { - artists.value = [ - ...artists.value - ..removeWhere((element) => element.id == artist.id) - ]; - }, - ), - child: Text(artist.name!), - ), - ); - - final tracksAutocomplete = SeedsMultiAutocomplete( - seeds: tracks, - enabled: enabled, - selectedItemDisplayType: SelectedItemDisplayType.list, - label: Text(context.l10n.tracks), - placeholder: Text(context.l10n.select_up_to_count_type( - leftSeedCount, - context.l10n.tracks, - )), - fetchSeeds: (textEditingValue) => spotify.invoke( - (api) => api.search - .get( - textEditingValue.text, - types: [SearchType.track], - ) - .first(6) - .then( - (v) => List.castFrom( - v.expand((e) => e.items ?? []).toList(), - ) - .where( - (element) => - tracks.value.none((track) => element.id == track.id), - ) - .toList(), - ), - ), - autocompleteOptionBuilder: (option, onSelected) => ButtonTile( - leading: Avatar( - initials: option.name!.substring(0, 1), - provider: UniversalImage.imageProvider( - (option.album?.images).asUrlString( - placeholder: ImagePlaceholder.artist, - ), - ), - ), - title: Text(option.name!), - subtitle: Text( - option.artists?.map((e) => e.name).join(", ") ?? - option.album?.name ?? - "", - ), - onPressed: () => onSelected(option), - style: ButtonVariance.ghost, - ), - displayStringForOption: (option) => option.name!, - selectedSeedBuilder: (option) => SimpleTrackTile( - track: option, - onDelete: () { - tracks.value = [ - ...tracks.value..removeWhere((element) => element.id == option.id) - ]; - }, - ), - ); - - final genreSelector = MultiSelect( - value: genres.value, - onChanged: (value) { - if (!enabled) return; - genres.value = value?.toList() ?? []; - }, - itemBuilder: (context, item) => Text(item), - popoverAlignment: Alignment.bottomCenter, - popupConstraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * .8, - ), - placeholder: Text( - context.l10n.select_up_to_count_type( - leftSeedCount, - context.l10n.genre, - ), - ), - popup: SelectPopup.builder( - searchPlaceholder: Text(context.l10n.select_genres), - builder: (context, searchQuery) { - final filteredGenres = searchQuery?.isNotEmpty != true - ? genresCollection.asData?.value ?? [] - : genresCollection.asData?.value - .where( - (item) => item - .toLowerCase() - .contains(searchQuery!.toLowerCase()), - ) - .toList() ?? - []; - - return SelectItemBuilder( - childCount: filteredGenres.length, - builder: (context, index) { - final option = filteredGenres[index]; - - return SelectItemButton( - value: option, - child: Text(option), - ); - }, - ); - }, - ).call, - ); - - final countrySelector = ValueListenableBuilder( - valueListenable: market, - builder: (context, value, _) { - return Select( - placeholder: Text(context.l10n.country), - value: market.value, - onChanged: (value) { - market.value = value!; - }, - popupConstraints: BoxConstraints( - maxHeight: MediaQuery.sizeOf(context).height * .8, - ), - popoverAlignment: Alignment.bottomCenter, - itemBuilder: (context, value) => Text(value.name), - popup: SelectPopup.builder( - searchPlaceholder: Text(context.l10n.search), - builder: (context, searchQuery) { - final filteredMarkets = searchQuery == null || searchQuery.isEmpty - ? spotifyMarkets - : spotifyMarkets - .where( - (item) => item.$1.name - .toLowerCase() - .contains(searchQuery.toLowerCase()), - ) - .toList(); - - return SelectItemBuilder( - childCount: filteredMarkets.length, - builder: (context, index) { - return SelectItemButton( - value: filteredMarkets[index].$1, - child: Text(filteredMarkets[index].$2), - ); - }, - ); - }, - ).call, - ); - }, - ); - - final controller = useScrollController(); - - return SafeArea( - bottom: false, - child: Scaffold( - headers: [ - TitleBar( - leading: const [BackButton()], - title: Text(context.l10n.generate), - ) - ], - child: Scrollbar( - controller: controller, - child: Center( - child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: Breakpoints.lg), - child: SafeArea( - child: LayoutBuilder(builder: (context, constrains) { - return ScrollConfiguration( - behavior: ScrollConfiguration.of(context) - .copyWith(scrollbars: false), - child: ListView( - controller: controller, - padding: const EdgeInsets.all(16), - children: [ - ValueListenableBuilder( - valueListenable: limit, - builder: (context, value, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.l10n.number_of_tracks_generate, - style: typography.semiBold, - ), - Row( - spacing: 5, - children: [ - Container( - width: 40, - height: 40, - alignment: Alignment.center, - decoration: BoxDecoration( - color: theme.colorScheme.primary - .withAlpha(25), - shape: BoxShape.circle, - ), - child: Text( - value.round().toString(), - style: typography.large.copyWith( - color: theme.colorScheme.primary, - ), - ), - ), - Expanded( - child: Slider( - value: SliderValue.single( - value.toDouble()), - min: 10, - max: 100, - divisions: 9, - onChanged: (value) { - limit.value = value.value.round(); - }, - ), - ) - ], - ) - ], - ); - }, - ), - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: countrySelector, - ), - const SizedBox(width: 16), - Expanded( - child: genreSelector, - ), - ], - ) - else ...[ - countrySelector, - const SizedBox(height: 16), - genreSelector, - ], - const SizedBox(height: 16), - if (constrains.mdAndUp) - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - child: artistAutoComplete, - ), - const SizedBox(width: 16), - Expanded( - child: tracksAutocomplete, - ), - ], - ) - else ...[ - artistAutoComplete, - const SizedBox(height: 16), - tracksAutocomplete, - ], - const SizedBox(height: 16), - RecommendationAttributeDials( - title: Text(context.l10n.acousticness), - values: ( - target: target.value.acousticness?.toDouble() ?? 0, - min: min.value.acousticness?.toDouble() ?? 0, - max: max.value.acousticness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - acousticness: value.target, - ); - min.value = min.value.copyWith( - acousticness: value.min, - ); - max.value = max.value.copyWith( - acousticness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.danceability), - values: ( - target: target.value.danceability?.toDouble() ?? 0, - min: min.value.danceability?.toDouble() ?? 0, - max: max.value.danceability?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - danceability: value.target, - ); - min.value = min.value.copyWith( - danceability: value.min, - ); - max.value = max.value.copyWith( - danceability: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.energy), - values: ( - target: target.value.energy?.toDouble() ?? 0, - min: min.value.energy?.toDouble() ?? 0, - max: max.value.energy?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - energy: value.target, - ); - min.value = min.value.copyWith( - energy: value.min, - ); - max.value = max.value.copyWith( - energy: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.instrumentalness), - values: ( - target: - target.value.instrumentalness?.toDouble() ?? 0, - min: min.value.instrumentalness?.toDouble() ?? 0, - max: max.value.instrumentalness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - instrumentalness: value.target, - ); - min.value = min.value.copyWith( - instrumentalness: value.min, - ); - max.value = max.value.copyWith( - instrumentalness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.liveness), - values: ( - target: target.value.liveness?.toDouble() ?? 0, - min: min.value.liveness?.toDouble() ?? 0, - max: max.value.liveness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - liveness: value.target, - ); - min.value = min.value.copyWith( - liveness: value.min, - ); - max.value = max.value.copyWith( - liveness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.loudness), - values: ( - target: target.value.loudness?.toDouble() ?? 0, - min: min.value.loudness?.toDouble() ?? 0, - max: max.value.loudness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - loudness: value.target, - ); - min.value = min.value.copyWith( - loudness: value.min, - ); - max.value = max.value.copyWith( - loudness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.speechiness), - values: ( - target: target.value.speechiness?.toDouble() ?? 0, - min: min.value.speechiness?.toDouble() ?? 0, - max: max.value.speechiness?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - speechiness: value.target, - ); - min.value = min.value.copyWith( - speechiness: value.min, - ); - max.value = max.value.copyWith( - speechiness: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.valence), - values: ( - target: target.value.valence?.toDouble() ?? 0, - min: min.value.valence?.toDouble() ?? 0, - max: max.value.valence?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - valence: value.target, - ); - min.value = min.value.copyWith( - valence: value.min, - ); - max.value = max.value.copyWith( - valence: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.popularity), - base: 100, - values: ( - target: target.value.popularity?.toDouble() ?? 0, - min: min.value.popularity?.toDouble() ?? 0, - max: max.value.popularity?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - popularity: value.target, - ); - min.value = min.value.copyWith( - popularity: value.min, - ); - max.value = max.value.copyWith( - popularity: value.max, - ); - }, - ), - RecommendationAttributeDials( - title: Text(context.l10n.key), - base: 11, - values: ( - target: target.value.key?.toDouble() ?? 0, - min: min.value.key?.toDouble() ?? 0, - max: max.value.key?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - key: value.target, - ); - min.value = min.value.copyWith( - key: value.min, - ); - max.value = max.value.copyWith( - key: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.duration), - values: ( - max: (max.value.durationMs ?? 0) / 1000, - target: (target.value.durationMs ?? 0) / 1000, - min: (min.value.durationMs ?? 0) / 1000, - ), - onChanged: (value) { - target.value = target.value.copyWith( - durationMs: (value.target * 1000).toInt(), - ); - min.value = min.value.copyWith( - durationMs: (value.min * 1000).toInt(), - ); - max.value = max.value.copyWith( - durationMs: (value.max * 1000).toInt(), - ); - }, - presets: { - context.l10n.short: (min: 50, target: 90, max: 120), - context.l10n.medium: ( - min: 120, - target: 180, - max: 200 - ), - context.l10n.long: (min: 480, target: 560, max: 640) - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.tempo), - values: ( - max: max.value.tempo?.toDouble() ?? 0, - target: target.value.tempo?.toDouble() ?? 0, - min: min.value.tempo?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - tempo: value.target, - ); - min.value = min.value.copyWith( - tempo: value.min, - ); - max.value = max.value.copyWith( - tempo: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.mode), - values: ( - max: max.value.mode?.toDouble() ?? 0, - target: target.value.mode?.toDouble() ?? 0, - min: min.value.mode?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - mode: value.target, - ); - min.value = min.value.copyWith( - mode: value.min, - ); - max.value = max.value.copyWith( - mode: value.max, - ); - }, - ), - RecommendationAttributeFields( - title: Text(context.l10n.time_signature), - values: ( - max: max.value.timeSignature?.toDouble() ?? 0, - target: target.value.timeSignature?.toDouble() ?? 0, - min: min.value.timeSignature?.toDouble() ?? 0, - ), - onChanged: (value) { - target.value = target.value.copyWith( - timeSignature: value.target, - ); - min.value = min.value.copyWith( - timeSignature: value.min, - ); - max.value = max.value.copyWith( - timeSignature: value.max, - ); - }, - ), - const Gap(20), - Center( - child: Button.primary( - leading: const Icon(SpotubeIcons.magic), - onPressed: artists.value.isEmpty && - tracks.value.isEmpty && - genres.value.isEmpty - ? null - : () { - final routeState = - GeneratePlaylistProviderInput( - seedArtists: artists.value - .map((a) => a.id!) - .toList(), - seedTracks: tracks.value - .map((t) => t.id!) - .toList(), - seedGenres: genres.value, - limit: limit.value, - max: max.value, - min: min.value, - target: target.value, - ); - context.navigateTo( - PlaylistGenerateResultRoute( - state: routeState, - ), - ); - }, - child: Text(context.l10n.generate), - ), - ), - ], - ), - ); - }), - ), - ), - ), - ), - ), - ); - } -} diff --git a/lib/pages/library/playlist_generate/playlist_generate_result.dart b/lib/pages/library/playlist_generate/playlist_generate_result.dart deleted file mode 100644 index 87ae9fe4..00000000 --- a/lib/pages/library/playlist_generate/playlist_generate_result.dart +++ /dev/null @@ -1,272 +0,0 @@ -import 'package:auto_route/auto_route.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/button/back_button.dart'; -import 'package:spotube/modules/library/playlist_generate/simple_track_tile.dart'; -import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; -import 'package:spotube/components/dialogs/playlist_add_track_dialog.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/models/spotify/recommendation_seeds.dart'; -import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; - -@RoutePage() -class PlaylistGenerateResultPage extends HookConsumerWidget { - static const name = "playlist_generate_result"; - - final GeneratePlaylistProviderInput state; - - const PlaylistGenerateResultPage({ - super.key, - required this.state, - }); - - @override - Widget build(BuildContext context, ref) { - final playlistNotifier = ref.watch(audioPlayerProvider.notifier); - - final generatedPlaylist = ref.watch(generatePlaylistProvider(state)); - - final selectedTracks = useState>( - generatedPlaylist.asData?.value.map((e) => e.id!).toList() ?? [], - ); - - useEffect(() { - if (generatedPlaylist.asData?.value != null) { - selectedTracks.value = - generatedPlaylist.asData!.value.map((e) => e.id!).toList(); - } - return null; - }, [generatedPlaylist.asData?.value]); - - final isAllTrackSelected = selectedTracks.value.length == - (generatedPlaylist.asData?.value.length ?? 0); - - return SafeArea( - bottom: false, - child: Scaffold( - headers: const [ - TitleBar(leading: [BackButton()]) - ], - child: generatedPlaylist.isLoading - ? Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - const CircularProgressIndicator(), - Text(context.l10n.generating_playlist), - ], - ), - ) - : Padding( - padding: const EdgeInsets.all(8.0), - child: ListView( - children: [ - GridView( - gridDelegate: - const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 2, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - mainAxisExtent: 32, - ), - shrinkWrap: true, - children: [ - Button.primary( - leading: const Icon(SpotubeIcons.play), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - await playlistNotifier.load( - generatedPlaylist.asData!.value - .where( - (e) => selectedTracks.value - .contains(e.id!), - ) - .toList(), - autoPlay: true, - ); - }, - child: Text(context.l10n.play), - ), - Button.primary( - leading: const Icon(SpotubeIcons.queueAdd), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - await playlistNotifier.addTracks( - generatedPlaylist.asData!.value.where( - (e) => - selectedTracks.value.contains(e.id!), - ), - ); - if (context.mounted) { - showToast( - context: context, - location: ToastLocation.topRight, - builder: (context, overlay) { - return SurfaceCard( - child: Text( - context.l10n.add_count_to_queue( - selectedTracks.value.length, - ), - ), - ); - }, - ); - } - }, - child: Text(context.l10n.add_to_queue), - ), - Button.primary( - leading: const Icon(SpotubeIcons.addFilled), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - final playlist = await showDialog( - context: context, - builder: (context) => PlaylistCreateDialog( - trackIds: selectedTracks.value, - ), - ); - - // if (playlist != null && context.mounted) { - // context.navigateTo( - // PlaylistRoute( - // id: playlist.id!, - // playlist: playlist, - // ), - // ); - // } - }, - child: Text(context.l10n.create_a_playlist), - ), - Button.primary( - leading: const Icon(SpotubeIcons.playlistAdd), - onPressed: selectedTracks.value.isEmpty - ? null - : () async { - final hasAdded = await showDialog( - context: context, - builder: (context) => - PlaylistAddTrackDialog( - openFromPlaylist: null, - tracks: selectedTracks.value - .map( - (e) => generatedPlaylist - .asData!.value - .firstWhere( - (element) => element.id == e, - ), - ) - .toList(), - ), - ); - - if (context.mounted && hasAdded == true) { - showToast( - context: context, - location: ToastLocation.topRight, - builder: (context, overlay) { - return SurfaceCard( - child: Text( - context.l10n.add_count_to_playlist( - selectedTracks.value.length, - ), - ), - ); - }, - ); - } - }, - child: Text(context.l10n.add_to_playlist), - ) - ], - ), - const SizedBox(height: 16), - if (generatedPlaylist.asData?.value != null) - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - context.l10n.selected_count_tracks( - selectedTracks.value.length, - ), - ), - Button.secondary( - onPressed: () { - if (isAllTrackSelected) { - selectedTracks.value = []; - } else { - selectedTracks.value = generatedPlaylist - .asData?.value - .map((e) => e.id!) - .toList() ?? - []; - } - }, - leading: const Icon(SpotubeIcons.selectionCheck), - child: Text( - isAllTrackSelected - ? context.l10n.deselect_all - : context.l10n.select_all, - ), - ), - ], - ), - const SizedBox(height: 8), - SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - for (final track - in generatedPlaylist.asData?.value ?? []) - Row( - spacing: 5, - children: [ - Checkbox( - state: selectedTracks.value.contains(track.id) - ? CheckboxState.checked - : CheckboxState.unchecked, - onChanged: (value) { - if (value == CheckboxState.checked) { - selectedTracks.value.add(track.id!); - } else { - selectedTracks.value.remove(track.id); - } - selectedTracks.value = - selectedTracks.value.toList(); - }, - ), - Expanded( - child: GestureDetector( - onTap: () { - selectedTracks.value.contains(track.id) - ? selectedTracks.value - .remove(track.id) - : selectedTracks.value.add(track.id!); - selectedTracks.value = - selectedTracks.value.toList(); - }, - child: SimpleTrackTile(track: track), - ), - ), - ], - ) - ], - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index 4534e531..c2cec373 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -17,7 +17,6 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/albums.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -61,7 +60,7 @@ class UserAlbumsPage extends HookConsumerWidget { child: Scaffold( child: material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(favoriteAlbumsProvider); + ref.invalidate(metadataPluginSavedAlbumsProvider); }, child: InterScrollbar( controller: controller, diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index 81db1451..6087f41c 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -19,7 +19,6 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/artists.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -65,7 +64,7 @@ class UserArtistsPage extends HookConsumerWidget { child: Scaffold( child: material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(followedArtistsProvider); + ref.invalidate(metadataPluginSavedArtistsProvider); }, child: InterScrollbar( controller: controller, diff --git a/lib/pages/library/user_downloads.dart b/lib/pages/library/user_downloads.dart index 1d8f560a..6566bed6 100644 --- a/lib/pages/library/user_downloads.dart +++ b/lib/pages/library/user_downloads.dart @@ -16,10 +16,7 @@ class UserDownloadsPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final downloadManager = ref.watch(downloadManagerProvider); - final history = [ - ...downloadManager.$history, - ...downloadManager.$backHistory, - ]; + final history = downloadManager.$backHistory; return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -51,7 +48,7 @@ class UserDownloadsPage extends HookConsumerWidget { child: ListView.builder( itemCount: history.length, itemBuilder: (context, index) { - return DownloadItem(track: history[index]); + return DownloadItem(track: history.elementAt(index)); }, ), ), diff --git a/lib/pages/library/user_local_tracks/local_folder.dart b/lib/pages/library/user_local_tracks/local_folder.dart index a6f3ad51..c256af7f 100644 --- a/lib/pages/library/user_local_tracks/local_folder.dart +++ b/lib/pages/library/user_local_tracks/local_folder.dart @@ -17,6 +17,7 @@ import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/string.dart'; import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/library/local_folder/cache_export_dialog.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; import 'package:spotube/components/expandable_search/expandable_search.dart'; @@ -24,9 +25,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/track_presentation/sort_tracks_dropdown.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; @@ -49,8 +48,8 @@ class LocalLibraryPage extends HookConsumerWidget { Future playLocalTracks( WidgetRef ref, - List tracks, { - LocalTrack? currentTrack, + List tracks, { + SpotubeLocalTrackObject? currentTrack, }) async { final playlist = ref.read(audioPlayerProvider); final playback = ref.read(audioPlayerProvider.notifier); @@ -64,7 +63,6 @@ class LocalLibraryPage extends HookConsumerWidget { autoPlay: true, ); } else if (isPlaylistPlaying && - currentTrack.id != null && currentTrack.id != playlist.activeTrack?.id) { await playback.jumpToTrack(currentTrack); } @@ -296,7 +294,8 @@ class LocalLibraryPage extends HookConsumerWidget { data: (tracks) { final sortedTracks = useMemoized(() { return ServiceUtils.sortTracks( - tracks[location] ?? [], + tracks[location] ?? + [], sortBy.value); }, [sortBy.value, tracks]); @@ -307,7 +306,7 @@ class LocalLibraryPage extends HookConsumerWidget { return sortedTracks .map((e) => ( weightedRatio( - "${e.name} - ${e.artists?.asString() ?? ""}", + "${e.name} - ${e.artists.asString()}", searchController.text, ), e, diff --git a/lib/pages/lyrics/lyrics.dart b/lib/pages/lyrics/lyrics.dart index 1bd58d61..b55dc02e 100644 --- a/lib/pages/lyrics/lyrics.dart +++ b/lib/pages/lyrics/lyrics.dart @@ -6,13 +6,13 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/hooks/utils/use_palette_color.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/lyrics/synced.dart'; import 'package:spotube/utils/platform.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -25,11 +25,11 @@ class LyricsPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final playlist = ref.watch(audioPlayerProvider); String albumArt = useMemoized( - () => (playlist.activeTrack?.album?.images).asUrlString( - index: (playlist.activeTrack?.album?.images?.length ?? 1) - 1, + () => (playlist.activeTrack?.album.images).asUrlString( + index: (playlist.activeTrack?.album.images.length ?? 1) - 1, placeholder: ImagePlaceholder.albumArt, ), - [playlist.activeTrack?.album?.images], + [playlist.activeTrack?.album.images], ); final palette = usePaletteColor(albumArt, ref); final selectedIndex = useState(0); diff --git a/lib/pages/lyrics/plain_lyrics.dart b/lib/pages/lyrics/plain_lyrics.dart index 0b5354a0..69f71cf4 100644 --- a/lib/pages/lyrics/plain_lyrics.dart +++ b/lib/pages/lyrics/plain_lyrics.dart @@ -5,14 +5,14 @@ import 'package:palette_generator/palette_generator.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/lyrics/zoom_controls.dart'; import 'package:spotube/components/shimmers/shimmer_lyrics.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/lyrics/synced.dart'; class PlainLyrics extends HookConsumerWidget { final PaletteColor palette; @@ -52,7 +52,7 @@ class PlainLyrics extends HookConsumerWidget { ), Center( child: Text( - playlist.activeTrack?.artists?.asString() ?? "", + playlist.activeTrack?.artists.asString() ?? "", style: (mediaQuery.mdAndUp ? typography.h4 : typography.large) .copyWith( color: palette.bodyTextColor, diff --git a/lib/pages/lyrics/synced_lyrics.dart b/lib/pages/lyrics/synced_lyrics.dart index b7423e14..5319d7ad 100644 --- a/lib/pages/lyrics/synced_lyrics.dart +++ b/lib/pages/lyrics/synced_lyrics.dart @@ -5,16 +5,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:palette_generator/palette_generator.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/lyrics/zoom_controls.dart'; import 'package:spotube/components/shimmers/shimmer_lyrics.dart'; -import 'package:spotube/extensions/artist_simple.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/hooks/controllers/use_auto_scroll_controller.dart'; import 'package:spotube/modules/lyrics/use_synced_lyrics.dart'; import 'package:scroll_to_index/scroll_to_index.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/lyrics/synced.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/logger/logger.dart'; @@ -117,7 +117,7 @@ class SyncedLyrics extends HookConsumerWidget { bottom: PreferredSize( preferredSize: const Size.fromHeight(40), child: Text( - playlist.activeTrack?.artists?.asString() ?? "", + playlist.activeTrack?.artists.asString() ?? "", style: mediaQuery.mdAndUp ? typography.h4 : typography.x2Large, ), diff --git a/lib/pages/player/lyrics.dart b/lib/pages/player/lyrics.dart index 01a4e921..e1aad553 100644 --- a/lib/pages/player/lyrics.dart +++ b/lib/pages/player/lyrics.dart @@ -5,8 +5,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/button/back_button.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/hooks/utils/use_palette_color.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/lyrics/plain_lyrics.dart'; import 'package:spotube/pages/lyrics/synced_lyrics.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -19,11 +19,11 @@ class PlayerLyricsPage extends HookConsumerWidget { Widget build(BuildContext context, ref) { final playlist = ref.watch(audioPlayerProvider); String albumArt = useMemoized( - () => (playlist.activeTrack?.album?.images).asUrlString( - index: (playlist.activeTrack?.album?.images?.length ?? 1) - 1, + () => (playlist.activeTrack?.album.images).asUrlString( + index: (playlist.activeTrack?.album.images.length ?? 1) - 1, placeholder: ImagePlaceholder.albumArt, ), - [playlist.activeTrack?.album?.images], + [playlist.activeTrack?.album.images], ); final selectedIndex = useState(0); final palette = usePaletteColor(albumArt, ref); diff --git a/lib/pages/playlist/liked_playlist.dart b/lib/pages/playlist/liked_playlist.dart index 5575b9d8..7c9a7fec 100644 --- a/lib/pages/playlist/liked_playlist.dart +++ b/lib/pages/playlist/liked_playlist.dart @@ -1,12 +1,11 @@ import 'package:flutter/material.dart' as material; import 'package:flutter/widgets.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/track_presentation/presentation_props.dart'; import 'package:spotube/components/track_presentation/track_presentation.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/playlist/playlist.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() @@ -21,12 +20,12 @@ class LikedPlaylistPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final likedTracks = ref.watch(likedTracksProvider); - final tracks = likedTracks.asData?.value ?? []; + final likedTracks = ref.watch(metadataPluginSavedTracksProvider); + final tracks = likedTracks.asData?.value.items ?? []; return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(likedTracksProvider); + ref.invalidate(metadataPluginSavedTracksProvider); }, child: TrackPresentation( options: TrackPresentationOptions( @@ -40,7 +39,7 @@ class LikedPlaylistPage extends HookConsumerWidget { return tracks.toList(); }, onRefresh: () async { - ref.invalidate(likedTracksProvider); + ref.invalidate(metadataPluginSavedTracksProvider); }, ), title: playlist.name, diff --git a/lib/pages/playlist/playlist.dart b/lib/pages/playlist/playlist.dart index 94c07ac1..1f6ca75d 100644 --- a/lib/pages/playlist/playlist.dart +++ b/lib/pages/playlist/playlist.dart @@ -9,8 +9,9 @@ import 'package:spotube/components/track_presentation/use_is_user_playlist.dart' import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:auto_route/auto_route.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; @RoutePage() class PlaylistPage extends HookConsumerWidget { @@ -30,8 +31,8 @@ class PlaylistPage extends HookConsumerWidget { .watch( metadataPluginSavedPlaylistsProvider.select( (value) => value.whenData( - (value) => (value.items as List) - .firstWhereOrNull((s) => s.id == _playlist.id), + (value) => + value.items.firstWhereOrNull((s) => s.id == _playlist.id), ), ), ) @@ -39,11 +40,11 @@ class PlaylistPage extends HookConsumerWidget { ?.value ?? _playlist; - final tracks = ref.watch(playlistTracksProvider(playlist.id)); + final tracks = ref.watch(metadataPluginPlaylistTracksProvider(playlist.id)); final tracksNotifier = - ref.watch(playlistTracksProvider(playlist.id).notifier); + ref.watch(metadataPluginPlaylistTracksProvider(playlist.id).notifier); final isFavoritePlaylist = - ref.watch(isFavoritePlaylistProvider(playlist.id)); + ref.watch(metadataPluginIsSavedPlaylistProvider(playlist.id)); final favoritePlaylistsNotifier = ref.watch(metadataPluginSavedPlaylistsProvider.notifier); @@ -52,9 +53,9 @@ class PlaylistPage extends HookConsumerWidget { return material.RefreshIndicator.adaptive( onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id)); - ref.invalidate(isFavoritePlaylistProvider(playlist.id)); - ref.invalidate(favoritePlaylistsProvider); + ref.invalidate(metadataPluginPlaylistTracksProvider(playlist.id)); + ref.invalidate(metadataPluginSavedPlaylistsProvider); + ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); }, child: TrackPresentation( options: TrackPresentationOptions( @@ -67,7 +68,7 @@ class PlaylistPage extends HookConsumerWidget { isLoading: tracks.isLoading || tracks.isLoadingNextPage, onFetchMore: tracksNotifier.fetchMore, onRefresh: () async { - ref.invalidate(playlistTracksProvider(playlist.id)); + ref.invalidate(metadataPluginPlaylistTracksProvider(playlist.id)); }, onFetchAll: () async { return await tracksNotifier.fetchAll(); diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index b6c4a2cd..a03a8fa4 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -1,16 +1,14 @@ -import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:sliver_tools/sliver_tools.dart'; import 'package:spotube/collections/fake.dart'; -import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/user.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:auto_route/auto_route.dart'; @@ -22,22 +20,22 @@ class ProfilePage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final me = ref.watch(meProvider); + final me = ref.watch(metadataPluginUserProvider); final meData = me.asData?.value ?? FakeData.user; - final userProperties = useMemoized( - () => { - context.l10n.email: meData.email ?? "N/A", - context.l10n.profile_followers: - meData.followers?.total.toString() ?? "N/A", - context.l10n.birthday: meData.birthdate ?? context.l10n.not_born, - context.l10n.country: spotifyMarkets - .firstWhere((market) => market.$1 == meData.country) - .$2, - context.l10n.subscription: meData.product ?? context.l10n.hacker, - }, - [meData], - ); + // final userProperties = useMemoized( + // () => { + // context.l10n.email: meData.email ?? "N/A", + // context.l10n.profile_followers: + // meData.followers?.total.toString() ?? "N/A", + // context.l10n.birthday: meData.birthdate ?? context.l10n.not_born, + // context.l10n.country: spotifyMarkets + // .firstWhere((market) => market.$1 == meData.country) + // .$2, + // context.l10n.subscription: meData.product ?? context.l10n.hacker, + // }, + // [meData], + // ); return SafeArea( child: Scaffold( @@ -72,7 +70,7 @@ class ProfilePage extends HookConsumerWidget { const SliverGap(10), SliverToBoxAdapter( child: Text( - meData.displayName ?? context.l10n.no_name, + meData.name, textAlign: TextAlign.center, ).h4(), ), @@ -97,42 +95,42 @@ class ProfilePage extends HookConsumerWidget { ), ), ), - SliverCrossAxisConstrained( - maxCrossAxisExtent: 500, - child: SliverToBoxAdapter( - child: Card( - child: Padding( - padding: const EdgeInsets.all(8.0), - child: Table( - columnWidths: const { - 0: FixedTableSize(120), - }, - defaultRowHeight: const FixedTableSize(40), - rows: [ - for (final MapEntry(:key, :value) - in userProperties.entries) - TableRow( - cells: [ - TableCell( - child: Padding( - padding: const EdgeInsets.all(6), - child: Text(key).large(), - ), - ), - TableCell( - child: Padding( - padding: const EdgeInsets.all(6), - child: Text(value), - ), - ), - ], - ) - ], - ), - ), - ), - ), - ), + // SliverCrossAxisConstrained( + // maxCrossAxisExtent: 500, + // child: SliverToBoxAdapter( + // child: Card( + // child: Padding( + // padding: const EdgeInsets.all(8.0), + // child: Table( + // columnWidths: const { + // 0: FixedTableSize(120), + // }, + // defaultRowHeight: const FixedTableSize(40), + // rows: [ + // for (final MapEntry(:key, :value) + // in userProperties.entries) + // TableRow( + // cells: [ + // TableCell( + // child: Padding( + // padding: const EdgeInsets.all(6), + // child: Text(key).large(), + // ), + // ), + // TableCell( + // child: Padding( + // padding: const EdgeInsets.all(6), + // child: Text(value), + // ), + // ), + // ], + // ) + // ], + // ), + // ), + // ), + // ), + // ), const SliverGap(200), ], ), diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 19043f42..b481300b 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -19,10 +19,13 @@ import 'package:spotube/pages/search/sections/artists.dart'; import 'package:spotube/pages/search/sections/playlists.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:auto_route/auto_route.dart'; +final searchTermStateProvider = StateProvider((ref) { + return ""; +}); + @RoutePage() class SearchPage extends HookConsumerWidget { static const name = "search"; diff --git a/lib/pages/search/sections/albums.dart b/lib/pages/search/sections/albums.dart index 249e0e6d..e8bc71fc 100644 --- a/lib/pages/search/sections/albums.dart +++ b/lib/pages/search/sections/albums.dart @@ -3,8 +3,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; class SearchAlbumsSection extends HookConsumerWidget { const SearchAlbumsSection({ diff --git a/lib/pages/search/sections/artists.dart b/lib/pages/search/sections/artists.dart index ac009cf4..9da3702c 100644 --- a/lib/pages/search/sections/artists.dart +++ b/lib/pages/search/sections/artists.dart @@ -3,8 +3,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; class SearchArtistsSection extends HookConsumerWidget { const SearchArtistsSection({ diff --git a/lib/pages/search/sections/playlists.dart b/lib/pages/search/sections/playlists.dart index 2c387f28..7e03bdeb 100644 --- a/lib/pages/search/sections/playlists.dart +++ b/lib/pages/search/sections/playlists.dart @@ -2,8 +2,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; class SearchPlaylistsSection extends HookConsumerWidget { const SearchPlaylistsSection({ diff --git a/lib/pages/search/sections/tracks.dart b/lib/pages/search/sections/tracks.dart index bacbbb57..6bc60045 100644 --- a/lib/pages/search/sections/tracks.dart +++ b/lib/pages/search/sections/tracks.dart @@ -2,15 +2,15 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/components/dialogs/prompt_dialog.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/track_tile/track_tile.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/pages/search/search.dart'; import 'package:spotube/provider/connect/connect.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; class SearchTracksSection extends HookConsumerWidget { const SearchTracksSection({ @@ -19,12 +19,9 @@ class SearchTracksSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final searchTrack = ref.watch(searchProvider(SearchType.track)); - - final searchTrackNotifier = - ref.watch(searchProvider(SearchType.track).notifier); - - final tracks = searchTrack.asData?.value.items.cast() ?? []; + final searchTerm = ref.watch(searchTermStateProvider); + final search = ref.watch(metadataPluginSearchAllProvider(searchTerm)); + final tracks = search.asData?.value.tracks ?? []; final playlistNotifier = ref.watch(audioPlayerProvider.notifier); final playlist = ref.watch(audioPlayerProvider); final theme = Theme.of(context); @@ -41,10 +38,8 @@ class SearchTracksSection extends HookConsumerWidget { style: theme.typography.h4, ), ), - if (searchTrack.isLoading) + if (search.isLoading) const CircularProgressIndicator() - else if (searchTrack.hasError) - Text(searchTrack.error.toString()) else ...tracks.mapIndexed((i, track) { return TrackTile( @@ -69,7 +64,7 @@ class SearchTracksSection extends HookConsumerWidget { ? await showPromptDialog( context: context, title: context.l10n.playing_track( - track.name!, + track.name, ), message: context.l10n.queue_clear_alert( playlist.tracks.length, @@ -92,7 +87,7 @@ class SearchTracksSection extends HookConsumerWidget { ? await showPromptDialog( context: context, title: context.l10n.playing_track( - track.name!, + track.name, ), message: context.l10n.queue_clear_alert( playlist.tracks.length, @@ -111,17 +106,6 @@ class SearchTracksSection extends HookConsumerWidget { }, ); }), - if (searchTrack.asData?.value.hasMore == true && tracks.isNotEmpty) - Center( - child: TextButton( - onPressed: searchTrack.isLoadingNextPage - ? null - : searchTrackNotifier.fetchMore, - child: searchTrack.isLoadingNextPage - ? const CircularProgressIndicator() - : Text(context.l10n.load_more), - ), - ) ], ); } diff --git a/lib/pages/settings/sections/language_region.dart b/lib/pages/settings/sections/language_region.dart index 44b364af..06d9ff01 100644 --- a/lib/pages/settings/sections/language_region.dart +++ b/lib/pages/settings/sections/language_region.dart @@ -1,10 +1,10 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/collections/language_codes.dart'; import 'package:spotube/collections/spotify_markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; +import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; import 'package:spotube/components/adaptive/adaptive_select_tile.dart'; import 'package:spotube/extensions/constrains.dart'; diff --git a/lib/pages/stats/albums/albums.dart b/lib/pages/stats/albums/albums.dart index 834837af..363e7962 100644 --- a/lib/pages/stats/albums/albums.dart +++ b/lib/pages/stats/albums/albums.dart @@ -8,7 +8,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/albums.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/stats/artists/artists.dart b/lib/pages/stats/artists/artists.dart index f3d2f0dd..340f7b4b 100644 --- a/lib/pages/stats/artists/artists.dart +++ b/lib/pages/stats/artists/artists.dart @@ -9,7 +9,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; @@ -27,7 +27,9 @@ class StatsArtistsPage extends HookConsumerWidget { ref.watch(historyTopTracksProvider(HistoryDuration.allTime).notifier); final artistsData = useMemoized( - () => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]); + () => topTracksNotifier.artists, + [topTracks.asData?.value], + ); return SafeArea( bottom: false, diff --git a/lib/pages/stats/fees/fees.dart b/lib/pages/stats/fees/fees.dart index 2f1e4107..7419b381 100644 --- a/lib/pages/stats/fees/fees.dart +++ b/lib/pages/stats/fees/fees.dart @@ -10,7 +10,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; @@ -31,7 +31,9 @@ class StatsStreamFeesPage extends HookConsumerWidget { ref.watch(historyTopTracksProvider(duration.value).notifier); final artistsData = useMemoized( - () => topTracks.asData?.value.artists ?? [], [topTracks.asData?.value]); + () => topTracksNotifier.artists, + [topTracks.asData?.value], + ); final total = useMemoized( () => artistsData.fold( diff --git a/lib/pages/stats/minutes/minutes.dart b/lib/pages/stats/minutes/minutes.dart index 2ee4c8d7..a6c95992 100644 --- a/lib/pages/stats/minutes/minutes.dart +++ b/lib/pages/stats/minutes/minutes.dart @@ -8,7 +8,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; @@ -52,8 +52,13 @@ class StatsMinutesPage extends HookConsumerWidget { return StatsTrackItem( track: track.track, info: Text( - context.l10n.count_mins(compactNumberFormatter - .format(track.count * track.track.duration!.inMinutes)), + context.l10n.count_mins( + compactNumberFormatter.format( + track.count * + Duration(milliseconds: track.track.durationMs) + .inMinutes, + ), + ), ), ); }, diff --git a/lib/pages/stats/playlists/playlists.dart b/lib/pages/stats/playlists/playlists.dart index 03ea5126..369066f7 100644 --- a/lib/pages/stats/playlists/playlists.dart +++ b/lib/pages/stats/playlists/playlists.dart @@ -8,7 +8,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/playlists.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/stats/streams/streams.dart b/lib/pages/stats/streams/streams.dart index 0d919a44..b2cc671d 100644 --- a/lib/pages/stats/streams/streams.dart +++ b/lib/pages/stats/streams/streams.dart @@ -8,7 +8,7 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/history/top.dart'; import 'package:spotube/provider/history/top/tracks.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:very_good_infinite_list/very_good_infinite_list.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 9f94650b..128c5103 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -4,19 +4,17 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; -import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; -import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/extensions/image.dart'; import 'package:spotube/extensions/list.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/tracks/track.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/extensions/constrains.dart'; @@ -42,7 +40,7 @@ class TrackPage extends HookConsumerWidget { final isActive = playlist.activeTrack?.id == trackId; - final trackQuery = ref.watch(trackProvider(trackId)); + final trackQuery = ref.watch(metadataPluginTrackProvider(trackId)); final track = trackQuery.asData?.value ?? FakeData.track; @@ -71,7 +69,7 @@ class TrackPage extends HookConsumerWidget { decoration: BoxDecoration( image: DecorationImage( image: UniversalImage.imageProvider( - track.album!.images.asUrlString( + track.album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), ), @@ -116,7 +114,7 @@ class TrackPage extends HookConsumerWidget { child: ClipRRect( borderRadius: BorderRadius.circular(10), child: UniversalImage( - path: track.album!.images.asUrlString( + path: track.album.images.asUrlString( placeholder: ImagePlaceholder.albumArt, ), height: 200, @@ -134,7 +132,7 @@ class TrackPage extends HookConsumerWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - track.name!, + track.name, ).large().semiBold(), const Gap(10), Row( @@ -162,7 +160,7 @@ class TrackPage extends HookConsumerWidget { const Gap(5), Flexible( child: ArtistLink( - artists: track.artists!, + artists: track.artists, hideOverflowArtist: false, ), ), diff --git a/lib/provider/audio_player/audio_player_streams.dart b/lib/provider/audio_player/audio_player_streams.dart index baf7b624..5b9731c5 100644 --- a/lib/provider/audio_player/audio_player_streams.dart +++ b/lib/provider/audio_player/audio_player_streams.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'dart:math'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; @@ -87,8 +86,8 @@ class AudioPlayerStreamListeners { String? lastScrobbled; return audioPlayer.positionStream.listen((position) async { try { - final uid = audioPlayerState.activeTrack is LocalTrack - ? (audioPlayerState.activeTrack as LocalTrack).path + final uid = audioPlayerState.activeTrack is SpotubeLocalTrackObject + ? (audioPlayerState.activeTrack as SpotubeLocalTrackObject).path : audioPlayerState.activeTrack?.id; if (audioPlayerState.activeTrack == null || diff --git a/lib/provider/blacklist_provider.dart b/lib/provider/blacklist_provider.dart index ff7ec8fb..f916c491 100644 --- a/lib/provider/blacklist_provider.dart +++ b/lib/provider/blacklist_provider.dart @@ -1,6 +1,5 @@ import 'package:collection/collection.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; @@ -47,9 +46,9 @@ class BlackListNotifier extends AsyncNotifier> { return containsTrack || containsTrackArtists; } - bool containsArtist(ArtistSimple artist) { + bool containsArtist(String artistId) { return state.asData?.value - .any((element) => element.elementId == artist.id) ?? + .any((element) => element.elementId == artistId) ?? false; } diff --git a/lib/provider/custom_spotify_endpoint_provider.dart b/lib/provider/custom_spotify_endpoint_provider.dart deleted file mode 100644 index 1f36282a..00000000 --- a/lib/provider/custom_spotify_endpoint_provider.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; -import 'package:spotube/services/custom_spotify_endpoints/spotify_endpoints.dart'; - -final customSpotifyEndpointProvider = Provider((ref) { - ref.watch(spotifyProvider); - final auth = ref.watch(authenticationProvider); - return CustomSpotifyEndpoints(auth.asData?.value?.accessToken.value ?? ""); -}); diff --git a/lib/provider/download_manager_provider.dart b/lib/provider/download_manager_provider.dart index a7e2b768..2a79f60d 100644 --- a/lib/provider/download_manager_provider.dart +++ b/lib/provider/download_manager_provider.dart @@ -112,10 +112,14 @@ class DownloadManagerProvider extends ChangeNotifier { return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name)); } - Future isActive(SpotubeFullTrackObject track) async { + bool isActive(SpotubeFullTrackObject track) { if ($backHistory.contains(track)) return true; - final sourcedTrack = await mapToSourcedTrack(track); + final sourcedTrack = $history.firstWhereOrNull( + (element) => element.query.id == track.id, + ); + + if (sourcedTrack == null) return false; return dl .getAllDownloads() @@ -196,9 +200,10 @@ class DownloadManagerProvider extends ChangeNotifier { } } - Future removeFromQueue(SourcedTrack track) async { - await dl.removeDownload(track.getUrlOfCodec(downloadCodec)); - $history.remove(track); + Future removeFromQueue(SpotubeFullTrackObject track) async { + final sourcedTrack = await mapToSourcedTrack(track); + await dl.removeDownload(sourcedTrack.getUrlOfCodec(downloadCodec)); + $history.remove(sourcedTrack); } Future pause(SpotubeFullTrackObject track) async { @@ -242,12 +247,26 @@ class DownloadManagerProvider extends ChangeNotifier { return sourcedTrack; } - ValueNotifier? getStatusNotifier(SourcedTrack track) { - return dl.getDownload(track.getUrlOfCodec(downloadCodec))?.status; + ValueNotifier? getStatusNotifier( + SpotubeFullTrackObject track, + ) { + final sourcedTrack = $history.firstWhereOrNull( + (element) => element.query.id == track.id, + ); + if (sourcedTrack == null) { + return null; + } + return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec))?.status; } - ValueNotifier? getProgressNotifier(SourcedTrack track) { - return dl.getDownload(track.getUrlOfCodec(downloadCodec))?.progress; + ValueNotifier? getProgressNotifier(SpotubeFullTrackObject track) { + final sourcedTrack = $history.firstWhereOrNull( + (element) => element.query.id == track.id, + ); + if (sourcedTrack == null) { + return null; + } + return dl.getDownload(sourcedTrack.getUrlOfCodec(downloadCodec))?.progress; } } diff --git a/lib/provider/history/top/albums.dart b/lib/provider/history/top/albums.dart index b11e62d2..1caad5cd 100644 --- a/lib/provider/history/top/albums.dart +++ b/lib/provider/history/top/albums.dart @@ -3,42 +3,19 @@ import 'dart:convert'; import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/history/top.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; -typedef PlaybackHistoryAlbum = ({int count, AlbumSimple album}); - -class HistoryTopAlbumsState extends PaginatedState { - HistoryTopAlbumsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - HistoryTopAlbumsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return HistoryTopAlbumsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} +typedef PlaybackHistoryAlbum = ({int count, SpotubeSimpleAlbumObject album}); class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier< - PlaybackHistoryAlbum, HistoryTopAlbumsState, HistoryDuration> { + PlaybackHistoryAlbum, HistoryDuration> { HistoryTopAlbumsNotifier() : super(); - Selectable createAlbumsQuery({int? limit, int? offset}) { + Selectable createAlbumsQuery( + {int? limit, int? offset}) { final database = ref.read(databaseProvider); final duration = switch (arg) { @@ -81,28 +58,28 @@ class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier< readsFrom: {database.historyTable}, ).map((row) { final data = row.read('data'); - final album = AlbumSimple.fromJson(jsonDecode(data)); + final album = SpotubeSimpleAlbumObject.fromJson(jsonDecode(data)); return album; }); } @override - fetch(arg, offset, limit) async { + fetch(offset, limit) async { final albumsQuery = createAlbumsQuery(limit: limit, offset: offset); final items = getAlbumsWithCount(await albumsQuery.get()); - return ( + return SpotubePaginationResponseObject( items: items, + limit: limit, hasMore: items.length == limit, - nextOffset: offset + limit, + nextOffset: (offset + limit).toInt(), + total: items.length, ); } @override build(arg) async { - final (items: albums, :hasMore, :nextOffset) = await fetch(arg, 0, 20); - final subscription = createAlbumsQuery().watch().listen((event) { if (state.asData == null) return; state = AsyncData(state.asData!.value.copyWith( @@ -115,18 +92,13 @@ class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier< subscription.cancel(); }); - return HistoryTopAlbumsState( - items: albums, - offset: nextOffset, - limit: 20, - hasMore: hasMore, - ); + return await fetch(0, 20); } List getAlbumsWithCount( - List albumsWithTrackAlbums, + List albumsWithTrackAlbums, ) { - return groupBy(albumsWithTrackAlbums, (album) => album.id!) + return groupBy(albumsWithTrackAlbums, (album) => album.id) .entries .map((entry) { return (count: entry.value.length, album: entry.value.first); @@ -137,6 +109,8 @@ class HistoryTopAlbumsNotifier extends FamilyPaginatedAsyncNotifier< } final historyTopAlbumsProvider = AsyncNotifierProviderFamily< - HistoryTopAlbumsNotifier, HistoryTopAlbumsState, HistoryDuration>( + HistoryTopAlbumsNotifier, + SpotubePaginationResponseObject, + HistoryDuration>( () => HistoryTopAlbumsNotifier(), ); diff --git a/lib/provider/history/top/playlists.dart b/lib/provider/history/top/playlists.dart index 19eb3622..1beabb80 100644 --- a/lib/provider/history/top/playlists.dart +++ b/lib/provider/history/top/playlists.dart @@ -1,40 +1,19 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/history/top.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; -typedef PlaybackHistoryPlaylist = ({int count, PlaylistSimple playlist}); - -class HistoryTopPlaylistsState extends PaginatedState { - HistoryTopPlaylistsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - HistoryTopPlaylistsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return HistoryTopPlaylistsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} +typedef PlaybackHistoryPlaylist = ({ + int count, + SpotubeSimplePlaylistObject playlist +}); class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< - PlaybackHistoryPlaylist, HistoryTopPlaylistsState, HistoryDuration> { + PlaybackHistoryPlaylist, HistoryDuration> { HistoryTopPlaylistsNotifier() : super(); SimpleSelectStatement<$HistoryTableTable, HistoryTableData> @@ -52,22 +31,22 @@ class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< } @override - fetch(arg, offset, limit) async { + fetch(offset, limit) async { final playlistsQuery = createPlaylistsQuery()..limit(limit, offset: offset); final items = getPlaylistsWithCount(await playlistsQuery.get()); - return ( + return SpotubePaginationResponseObject( items: items, - hasMore: items.length == limit, nextOffset: offset + limit, + total: items.length, + limit: limit, + hasMore: items.length == limit, ); } @override build(arg) async { - final (items: playlists, :hasMore, :nextOffset) = await fetch(arg, 0, 20); - final subscription = createPlaylistsQuery().watch().listen((event) { if (state.asData == null) return; state = AsyncData(state.asData!.value.copyWith( @@ -80,18 +59,13 @@ class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< subscription.cancel(); }); - return HistoryTopPlaylistsState( - items: playlists, - offset: nextOffset, - limit: 20, - hasMore: hasMore, - ); + return await fetch(0, 20); } List getPlaylistsWithCount( List playlists, ) { - return groupBy(playlists, (playlist) => playlist.playlist!.id!) + return groupBy(playlists, (playlist) => playlist.playlist!.id) .entries .map((entry) { return ( @@ -105,6 +79,8 @@ class HistoryTopPlaylistsNotifier extends FamilyPaginatedAsyncNotifier< } final historyTopPlaylistsProvider = AsyncNotifierProviderFamily< - HistoryTopPlaylistsNotifier, HistoryTopPlaylistsState, HistoryDuration>( + HistoryTopPlaylistsNotifier, + SpotubePaginationResponseObject, + HistoryDuration>( () => HistoryTopPlaylistsNotifier(), ); diff --git a/lib/provider/history/top/tracks.dart b/lib/provider/history/top/tracks.dart index 3c057e56..5c1dbdbf 100644 --- a/lib/provider/history/top/tracks.dart +++ b/lib/provider/history/top/tracks.dart @@ -1,65 +1,18 @@ import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/provider/history/top.dart'; -import 'package:spotube/provider/spotify/spotify.dart'; +import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; -typedef PlaybackHistoryTrack = ({int count, Track track}); -typedef PlaybackHistoryArtist = ({int count, Artist artist}); - -class HistoryTopTracksState extends PaginatedState { - HistoryTopTracksState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - List get artists { - return getArtistsWithCount( - items.expand((e) => e.track.artists ?? []), - ); - } - - List getArtistsWithCount(Iterable artists) { - return groupBy(artists, (artist) => artist.id!) - .entries - .map((entry) { - return ( - count: entry.value.length, - - /// Previously, due to a bug, artist images were not being saved. - /// Now it's fixed, but we need to handle the case where images are null. - /// So we take the first artist with images if available, otherwise the first one. - artist: entry.value.firstWhereOrNull((a) => a.images != null) ?? - entry.value.first, - ); - }) - .sorted((a, b) => b.count.compareTo(a.count)) - .toList(); - } - - @override - HistoryTopTracksState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return HistoryTopTracksState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} +typedef PlaybackHistoryTrack = ({int count, SpotubeTrackObject track}); +typedef PlaybackHistoryArtist = ({int count, SpotubeSimpleArtistObject artist}); class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< - PlaybackHistoryTrack, HistoryTopTracksState, HistoryDuration> { + PlaybackHistoryTrack, HistoryDuration> { HistoryTopTracksNotifier() : super(); SimpleSelectStatement<$HistoryTableTable, HistoryTableData> @@ -97,35 +50,45 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< List entries, ) async { final nonImageArtistTracks = - entries.where((e) => e.track!.artists!.any((a) => a.images == null)); + entries.where((e) => e.track!.artists.any((a) => a.images == null)); if (nonImageArtistTracks.isEmpty) return; final artistIds = nonImageArtistTracks - .map((e) => e.track!.artists!.map((a) => a.id!)) + .map((e) => e.track!.artists.map((a) => a.id)) .expand((e) => e) .toSet() .toList(); if (artistIds.isEmpty) return; - final artists = await ref.read(spotifyProvider).api.artists.list(artistIds); + final artists = await Future.wait([ + for (final id in artistIds) + ref.read(metadataPluginArtistProvider(id).future), + ]); final imagedArtistTracks = nonImageArtistTracks.map((e) { - final track = e.track!; - final includedArtists = track.artists! - .map((a) => artists.firstWhereOrNull((artist) => artist.id == a.id)) + var track = e.track!; + final includedArtists = track.artists + .map((a) { + final fullArtist = + artists.firstWhereOrNull((artist) => artist.id == a.id); + + return fullArtist != null + ? a.copyWith(images: fullArtist.images) + : a; + }) .nonNulls .toList(); - track.artists = includedArtists; + track = track.copyWith(artists: includedArtists); return e.copyWith(data: track.toJson()); }); assert( imagedArtistTracks - .every((e) => e.track!.artists!.every((a) => a.images != null)), + .every((e) => e.track!.artists.every((a) => a.images != null)), 'Tracks artists should have images', ); @@ -139,24 +102,24 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< } @override - fetch(arg, offset, limit) async { + fetch(offset, limit) async { final tracksQuery = createTracksQuery()..limit(limit, offset: offset); final entries = await tracksQuery.get(); final items = getTracksWithCount(entries); - return ( + return SpotubePaginationResponseObject( items: items, - hasMore: items.length == limit, nextOffset: offset + limit, + total: items.length, + limit: limit, + hasMore: items.length == limit, ); } @override build(arg) async { - final (items: tracks, :hasMore, :nextOffset) = await fetch(arg, 0, 20); - final subscription = createTracksQuery().watch().listen((event) { if (state.asData == null) return; state = AsyncData(state.asData!.value.copyWith( @@ -169,20 +132,41 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< subscription.cancel(); }); - return HistoryTopTracksState( - items: tracks, - offset: nextOffset, - limit: 20, - hasMore: hasMore, + return await fetch(0, 20); + } + + List get artists { + return getArtistsWithCount( + state.asData?.value.items.expand((e) => e.track.artists) ?? [], ); } + List getArtistsWithCount( + Iterable artists, + ) { + return groupBy(artists, (artist) => artist.id) + .entries + .map((entry) { + return ( + count: entry.value.length, + + /// Previously, due to a bug, artist images were not being saved. + /// Now it's fixed, but we need to handle the case where images are null. + /// So we take the first artist with images if available, otherwise the first one. + artist: entry.value.firstWhereOrNull((a) => a.images != null) ?? + entry.value.first, + ); + }) + .sorted((a, b) => b.count.compareTo(a.count)) + .toList(); + } + List getTracksWithCount(List tracks) { fixImageNotLoadingForArtistIssue(tracks); return groupBy( tracks, - (track) => track.track!.id!, + (track) => track.track!.id, ) .entries .map((entry) { @@ -194,7 +178,7 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< /// So we take the first artist with images if available, otherwise the first one. track: entry.value .firstWhereOrNull( - (t) => t.track!.artists!.every((a) => a.images != null)) + (t) => t.track!.artists.every((a) => a.images != null)) ?.track! ?? entry.value.first.track!, ); @@ -205,6 +189,8 @@ class HistoryTopTracksNotifier extends FamilyPaginatedAsyncNotifier< } final historyTopTracksProvider = AsyncNotifierProviderFamily< - HistoryTopTracksNotifier, HistoryTopTracksState, HistoryDuration>( + HistoryTopTracksNotifier, + SpotubePaginationResponseObject, + HistoryDuration>( () => HistoryTopTracksNotifier(), ); diff --git a/lib/provider/local_tracks/local_tracks_provider.dart b/lib/provider/local_tracks/local_tracks_provider.dart index c69d3169..3a94815b 100644 --- a/lib/provider/local_tracks/local_tracks_provider.dart +++ b/lib/provider/local_tracks/local_tracks_provider.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; @@ -9,9 +10,6 @@ import 'package:mime/mime.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/extensions/track.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; // ignore: depend_on_referenced_packages import 'package:flutter_rust_bridge/flutter_rust_bridge.dart' show FrbException; @@ -38,10 +36,10 @@ const imgMimeToExt = { }; final localTracksProvider = - FutureProvider>>((ref) async { + FutureProvider>>((ref) async { try { if (kIsWeb) return {}; - final Map> libraryToTracks = {}; + final Map> libraryToTracks = {}; final downloadLocation = ref.watch( userPreferencesProvider.select((s) => s.downloadLocation), @@ -121,14 +119,11 @@ final localTracksProvider = final tracksFromMetadata = filesWithMetadata .map( - (fileWithMetadata) => LocalTrack.fromTrack( - track: Track().fromFile( - fileWithMetadata["file"], - metadata: fileWithMetadata["metadata"], - art: fileWithMetadata["art"], - ), - path: fileWithMetadata["file"].path, - ), + (fileWithMetadata) => SpotubeTrackObject.localTrackFromFile( + fileWithMetadata["file"] as File, + metadata: fileWithMetadata["metadata"] as Metadata?, + art: fileWithMetadata["art"] as String?, + ) as SpotubeLocalTrackObject, ) .toList(); diff --git a/lib/provider/spotify/lyrics/synced.dart b/lib/provider/lyrics/synced.dart similarity index 54% rename from lib/provider/spotify/lyrics/synced.dart rename to lib/provider/lyrics/synced.dart index ff2a73f1..0c43d53e 100644 --- a/lib/provider/spotify/lyrics/synced.dart +++ b/lib/provider/lyrics/synced.dart @@ -1,54 +1,67 @@ -part of '../spotify.dart'; +import 'dart:async'; -class SyncedLyricsNotifier extends FamilyAsyncNotifier { - Track get _track => arg!; +import 'package:dio/dio.dart'; +import 'package:drift/drift.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:lrc/lrc.dart'; +import 'package:package_info_plus/package_info_plus.dart'; +import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/lyrics.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/database/database.dart'; +import 'package:spotube/services/dio/dio.dart'; +import 'package:spotube/services/logger/logger.dart'; - Future getSpotifyLyrics(String? token) async { - final res = await globalDio.getUri( - Uri.parse( - "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token", - ), - options: Options( - headers: { - "User-Agent": - "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36", - "App-platform": "WebPlayer", - "authorization": "Bearer $token" - }, - responseType: ResponseType.json, - validateStatus: (status) => true, - ), - ); +class SyncedLyricsNotifier + extends FamilyAsyncNotifier { + SpotubeTrackObject get _track => arg!; - if (res.statusCode != 200) { - return SubtitleSimple( - lyrics: [], - name: _track.name!, - uri: res.realUri, - rating: 0, - provider: "Spotify", - ); - } - final linesRaw = - Map.castFrom(res.data)["lyrics"] - ?["lines"] as List?; + // Future getSpotifyLyrics(String? token) async { + // final res = await globalDio.getUri( + // Uri.parse( + // "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token", + // ), + // options: Options( + // headers: { + // "User-Agent": + // "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36", + // "App-platform": "WebPlayer", + // "authorization": "Bearer $token" + // }, + // responseType: ResponseType.json, + // validateStatus: (status) => true, + // ), + // ); - final lines = linesRaw?.map((line) { - return LyricSlice( - time: Duration(milliseconds: int.parse(line["startTimeMs"])), - text: line["words"] as String, - ); - }).toList() ?? - []; + // if (res.statusCode != 200) { + // return SubtitleSimple( + // lyrics: [], + // name: _track.name!, + // uri: res.realUri, + // rating: 0, + // provider: "Spotify", + // ); + // } + // final linesRaw = + // Map.castFrom(res.data)["lyrics"] + // ?["lines"] as List?; - return SubtitleSimple( - lyrics: lines, - name: _track.name!, - uri: res.realUri, - rating: 100, - provider: "Spotify", - ); - } + // final lines = linesRaw?.map((line) { + // return LyricSlice( + // time: Duration(milliseconds: int.parse(line["startTimeMs"])), + // text: line["words"] as String, + // ); + // }).toList() ?? + // []; + + // return SubtitleSimple( + // lyrics: lines, + // name: _track.name!, + // uri: res.realUri, + // rating: 100, + // provider: "Spotify", + // ); + // } /// Lyrics credits: [lrclib.net](https://lrclib.net) and their contributors /// Thanks for their generous public API @@ -61,10 +74,10 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { host: "lrclib.net", path: "/api/get", queryParameters: { - "artist_name": _track.artists?.first.name, + "artist_name": _track.artists.first.name, "track_name": _track.name, - "album_name": _track.album?.name, - "duration": _track.duration?.inSeconds.toString(), + "album_name": _track.album.name, + "duration": (_track.durationMs / 1000).toInt().toString(), }, ), options: Options( @@ -79,7 +92,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { if (res.statusCode != 200) { return SubtitleSimple( lyrics: [], - name: _track.name!, + name: _track.name, uri: res.realUri, rating: 0, provider: "LRCLib", @@ -99,7 +112,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { if (syncedLyrics?.isNotEmpty == true) { return SubtitleSimple( lyrics: syncedLyrics!, - name: _track.name!, + name: _track.name, uri: res.realUri, rating: 100, provider: "LRCLib", @@ -113,7 +126,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { return SubtitleSimple( lyrics: plainLyrics, - name: _track.name!, + name: _track.name, uri: res.realUri, rating: 0, provider: "LRCLib", @@ -124,26 +137,18 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { FutureOr build(track) async { try { final database = ref.watch(databaseProvider); - final spotify = ref.watch(spotifyProvider); - final auth = await ref.watch(authenticationProvider.future); if (track == null) { throw "No track currently"; } final cachedLyrics = await (database.select(database.lyricsTable) - ..where((tbl) => tbl.trackId.equals(track.id!))) + ..where((tbl) => tbl.trackId.equals(track.id))) .map((row) => row.data) .getSingleOrNull(); SubtitleSimple? lyrics = cachedLyrics; - final token = await spotify.invoke((api) => api.getCredentials()); - - if ((lyrics == null || lyrics.lyrics.isEmpty) && auth != null) { - lyrics = await getSpotifyLyrics(token.accessToken); - } - if (lyrics == null || lyrics.lyrics.isEmpty || lyrics.lyrics.length <= 5) { @@ -157,7 +162,7 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { if (cachedLyrics == null || cachedLyrics.lyrics.isEmpty) { await database.into(database.lyricsTable).insert( LyricsTableCompanion.insert( - trackId: track.id!, + trackId: track.id, data: lyrics, ), mode: InsertMode.replace, @@ -174,13 +179,13 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { final syncedLyricsDelayProvider = StateProvider((ref) => 0); -final syncedLyricsProvider = - AsyncNotifierProviderFamily( +final syncedLyricsProvider = AsyncNotifierProviderFamily( () => SyncedLyricsNotifier(), ); final syncedLyricsMapProvider = - FutureProvider.family((ref, Track? track) async { + FutureProvider.family((ref, SpotubeTrackObject? track) async { final syncedLyrics = await ref.watch(syncedLyricsProvider(track).future); final isStaticLyrics = diff --git a/lib/provider/metadata_plugin/album/releases.dart b/lib/provider/metadata_plugin/album/releases.dart new file mode 100644 index 00000000..0d557d0a --- /dev/null +++ b/lib/provider/metadata_plugin/album/releases.dart @@ -0,0 +1,29 @@ +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/utils/paginated.dart'; + +class MetadataPluginAlbumReleasesNotifier + extends PaginatedAsyncNotifier { + @override + Future> fetch( + int offset, + int limit, + ) async { + return await (await metadataPlugin) + .album + .releases(limit: limit, offset: offset); + } + + @override + build() async { + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginAlbumReleasesProvider = AsyncNotifierProvider< + MetadataPluginAlbumReleasesNotifier, + SpotubePaginationResponseObject>( + () => MetadataPluginAlbumReleasesNotifier(), +); diff --git a/lib/provider/metadata_plugin/artist/wikipedia.dart b/lib/provider/metadata_plugin/artist/wikipedia.dart new file mode 100644 index 00000000..81fcc77c --- /dev/null +++ b/lib/provider/metadata_plugin/artist/wikipedia.dart @@ -0,0 +1,18 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/services/wikipedia/wikipedia.dart'; +import 'package:wikipedia_api/wikipedia_api.dart'; + +final artistWikipediaSummaryProvider = + FutureProvider.autoDispose.family( + (ref, artist) async { + final query = artist.name.replaceAll(" ", "_"); + final res = await wikipedia.pageContent.pageSummaryTitleGet(query); + + if (res?.type != "standard") { + return await wikipedia.pageContent + .pageSummaryTitleGet("${query}_(singer)"); + } + return res; + }, +); diff --git a/lib/provider/metadata_plugin/library/playlists.dart b/lib/provider/metadata_plugin/library/playlists.dart index 1ebe2a63..40db7951 100644 --- a/lib/provider/metadata_plugin/library/playlists.dart +++ b/lib/provider/metadata_plugin/library/playlists.dart @@ -63,16 +63,16 @@ class MetadataPluginSavedPlaylistsNotifier ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); } - Future delete(SpotubeSimplePlaylistObject playlist) async { + Future delete(String playlistId) async { await update((state) async { - (await metadataPlugin).playlist.deletePlaylist(playlist.id); + (await metadataPlugin).playlist.deletePlaylist(playlistId); return state.copyWith( - items: state.items.where((e) => (e).id != playlist.id).toList(), + items: state.items.where((e) => (e).id != playlistId).toList(), ); }); - ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlist.id)); - ref.invalidate(metadataPluginPlaylistTracksProvider(playlist.id)); + ref.invalidate(metadataPluginIsSavedPlaylistProvider(playlistId)); + ref.invalidate(metadataPluginPlaylistTracksProvider(playlistId)); } Future addTracks(String playlistId, List trackIds) async { diff --git a/lib/provider/metadata_plugin/playlist/playlist.dart b/lib/provider/metadata_plugin/playlist/playlist.dart index 9752cb21..8d5b71be 100644 --- a/lib/provider/metadata_plugin/playlist/playlist.dart +++ b/lib/provider/metadata_plugin/playlist/playlist.dart @@ -1,22 +1,133 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/library/playlists.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/services/metadata/endpoints/error.dart'; +import 'package:spotube/services/metadata/metadata.dart'; -final metadataPluginPlaylistProvider = - FutureProvider.autoDispose.family( - (ref, id) async { - ref.cacheFor(); - - final metadataPlugin = await ref.watch(metadataPluginProvider.future); +class MetadataPluginPlaylistNotifier + extends AutoDisposeFamilyAsyncNotifier { + Future get metadataPlugin async { + final metadataPlugin = await ref.read(metadataPluginProvider.future); if (metadataPlugin == null) { throw MetadataPluginException.noDefaultPlugin( - "No metadata plugin is not set", + "Metadata plugin is not set", ); } - return metadataPlugin.playlist.getPlaylist(id); - }, + return metadataPlugin; + } + + @override + build(playlistId) async { + ref.cacheFor(); + + return (await metadataPlugin).playlist.getPlaylist(playlistId); + } + + Future create({ + required String name, + String? description, + bool? public, + bool? collaborative, + void Function(dynamic error)? onError, + }) async { + final userId = await ref + .read(metadataPluginUserProvider.selectAsync((data) => data?.id)); + if (userId == null) { + throw Exception('User ID is not available. Please log in first.'); + } + await update( + (prev) async { + try { + final playlist = await (await metadataPlugin).playlist.create( + userId, + name: name, + description: description, + public: public, + collaborative: collaborative, + ); + return playlist!; + } catch (e) { + onError?.call(e); + rethrow; + } + }, + ); + } + + Future modify({ + String? name, + String? description, + bool? public, + bool? collaborative, + void Function(dynamic error)? onError, + }) async { + try { + if (name == null && + description == null && + public == null && + collaborative == null) { + throw Exception('No modifications provided.'); + } + await (await metadataPlugin).playlist.update( + arg, + name: name, + description: description, + public: public, + collaborative: collaborative, + ); + ref.invalidateSelf(); + } on Exception catch (e) { + onError?.call(e); + rethrow; + } + } + + Future addTracks(List trackIds, + [void Function(dynamic error)? onError]) async { + if (state.value == null) return; + + try { + await ref + .read(metadataPluginSavedPlaylistsProvider.notifier) + .addTracks(arg, trackIds); + } catch (e) { + onError?.call(e); + rethrow; + } + } + + Future removeTracks(List trackIds, + [void Function(dynamic error)? onError]) async { + try { + if (state.value == null) return; + + await ref + .read(metadataPluginSavedPlaylistsProvider.notifier) + .removeTracks(arg, trackIds); + } catch (e) { + onError?.call(e); + rethrow; + } + } + + Future delete() async { + if (state.value == null) return; + final userId = await ref + .read(metadataPluginUserProvider.selectAsync((data) => data?.id)); + if (userId == null || userId != state.value!.owner.id) { + throw Exception('You can only delete your own playlists.'); + } + + await ref.read(metadataPluginSavedPlaylistsProvider.notifier).delete(arg); + } +} + +final metadataPluginPlaylistProvider = AutoDisposeAsyncNotifierProviderFamily< + MetadataPluginPlaylistNotifier, SpotubeFullPlaylistObject, String>( + () => MetadataPluginPlaylistNotifier(), ); diff --git a/lib/provider/metadata_plugin/tracks/track.dart b/lib/provider/metadata_plugin/tracks/track.dart new file mode 100644 index 00000000..261e967d --- /dev/null +++ b/lib/provider/metadata_plugin/tracks/track.dart @@ -0,0 +1,16 @@ +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/services/metadata/endpoints/error.dart'; + +final metadataPluginTrackProvider = + FutureProvider.family((ref, trackId) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No metadata plugin is set as default."); + } + + return metadataPlugin.track.getTrack(trackId); +}); diff --git a/lib/provider/metadata_plugin/utils/family_paginated.dart b/lib/provider/metadata_plugin/utils/family_paginated.dart index a994bdab..97717d04 100644 --- a/lib/provider/metadata_plugin/utils/family_paginated.dart +++ b/lib/provider/metadata_plugin/utils/family_paginated.dart @@ -25,8 +25,7 @@ abstract class FamilyPaginatedAsyncNotifier state.value!.items.isEmpty ? [] : state.value!.items.cast(); final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]) - as SpotubePaginationResponseObject; + return newState.copyWith(items: [...oldItems, ...items]); }, ); } @@ -46,8 +45,7 @@ abstract class FamilyPaginatedAsyncNotifier hasMore = newState.hasMore; final oldItems = state.items.isEmpty ? [] : state.items.cast(); final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]) - as SpotubePaginationResponseObject; + return newState.copyWith(items: [...oldItems, ...items]); }); } @@ -74,7 +72,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier return newState.copyWith(items: [ ...state.value!.items.cast(), ...newState.items.cast(), - ]) as SpotubePaginationResponseObject; + ]); }, ); } @@ -95,7 +93,7 @@ abstract class AutoDisposeFamilyPaginatedAsyncNotifier return newState.copyWith(items: [ ...state.items.cast(), ...newState.items.cast(), - ]) as SpotubePaginationResponseObject; + ]); }); } diff --git a/lib/provider/server/track_sources.dart b/lib/provider/server/track_sources.dart index 2112c2af..4a0f29ca 100644 --- a/lib/provider/server/track_sources.dart +++ b/lib/provider/server/track_sources.dart @@ -23,10 +23,7 @@ class TrackSourcesNotifier }); } - Future copyWithSibling( - TrackSourceInfo info, - TrackSourceQuery query, - ) async { + Future copyWithSibling() async { return await update((prev) async { return prev.copyWithSibling(); }); diff --git a/lib/provider/spotify/album/favorite.dart b/lib/provider/spotify/album/favorite.dart deleted file mode 100644 index 157ab225..00000000 --- a/lib/provider/spotify/album/favorite.dart +++ /dev/null @@ -1,91 +0,0 @@ -part of '../spotify.dart'; - -class FavoriteAlbumState extends PaginatedState { - FavoriteAlbumState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - FavoriteAlbumState copyWith({items, offset, limit, hasMore}) { - return FavoriteAlbumState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class FavoriteAlbumNotifier - extends PaginatedAsyncNotifier { - @override - Future> fetch(int offset, int limit) async { - return await spotify - .invoke( - (api) => api.me.savedAlbums().getPage(limit, offset), - ) - .then( - (value) => value.items?.toList() ?? [], - ); - } - - @override - build() async { - ref.watch(spotifyProvider); - final items = await fetch(0, 20); - return FavoriteAlbumState( - items: items, - offset: 0, - limit: 20, - hasMore: items.length == 20, - ); - } - - Future addFavorites(List ids) async { - if (state.value == null) return; - - state = await AsyncValue.guard(() async { - await spotify.invoke((api) => api.me.saveAlbums(ids)); - final albums = await spotify.invoke( - (api) => api.albums.list(ids), - ); - - return state.value!.copyWith( - items: [ - ...state.value!.items, - ...albums, - ], - ); - }); - - for (final id in ids) { - ref.invalidate(albumsIsSavedProvider(id)); - } - } - - Future removeFavorites(List ids) async { - if (state.value == null) return; - - state = await AsyncValue.guard(() async { - await spotify.invoke((api) => api.me.removeAlbums(ids)); - - return state.value!.copyWith( - items: state.value!.items - .where((element) => !ids.contains(element.id)) - .toList(), - ); - }); - - for (final id in ids) { - ref.invalidate(albumsIsSavedProvider(id)); - } - } -} - -final favoriteAlbumsProvider = - AsyncNotifierProvider( - () => FavoriteAlbumNotifier(), -); diff --git a/lib/provider/spotify/album/is_saved.dart b/lib/provider/spotify/album/is_saved.dart deleted file mode 100644 index aa48dfa0..00000000 --- a/lib/provider/spotify/album/is_saved.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of '../spotify.dart'; - -final albumsIsSavedProvider = FutureProvider.autoDispose.family( - (ref, albumId) async { - final spotify = ref.watch(spotifyProvider); - return spotify.invoke( - (api) => api.me.containsSavedAlbums([albumId]).then( - (value) => value[albumId] ?? false, - ), - ); - }, -); diff --git a/lib/provider/spotify/album/releases.dart b/lib/provider/spotify/album/releases.dart deleted file mode 100644 index 25bb46b4..00000000 --- a/lib/provider/spotify/album/releases.dart +++ /dev/null @@ -1,87 +0,0 @@ -part of '../spotify.dart'; - -class AlbumReleasesState extends PaginatedState { - AlbumReleasesState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - AlbumReleasesState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return AlbumReleasesState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class AlbumReleasesNotifier - extends PaginatedAsyncNotifier { - AlbumReleasesNotifier() : super(); - - @override - fetch(int offset, int limit) async { - final market = ref.read(userPreferencesProvider).market; - - final albums = await spotify.invoke( - (api) => api.browse.newReleases(country: market).getPage(limit, offset), - ); - - return albums.items?.map((album) => album.toAlbum()).toList() ?? []; - } - - @override - build() async { - ref.watch(spotifyProvider); - ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - ref.watch(allFollowedArtistsProvider); - - final albums = await fetch(0, 20); - - return AlbumReleasesState( - items: albums, - offset: 0, - limit: 20, - hasMore: albums.length == 20, - ); - } -} - -final albumReleasesProvider = - AsyncNotifierProvider( - () => AlbumReleasesNotifier(), -); - -final userArtistAlbumReleasesProvider = Provider>((ref) { - final newReleases = ref.watch(albumReleasesProvider); - final userArtistsQuery = ref.watch(allFollowedArtistsProvider); - - if (newReleases.isLoading || userArtistsQuery.isLoading) { - return const []; - } - - final userArtists = - userArtistsQuery.asData?.value.map((s) => s.id!).toList() ?? const []; - - final allReleases = newReleases.asData?.value.items; - final userArtistReleases = allReleases?.where((album) { - return album.artists?.any((artist) => userArtists.contains(artist.id!)) == - true; - }).toList(); - - if (userArtistReleases?.isEmpty == true) { - return allReleases?.toList() ?? []; - } - return userArtistReleases ?? []; -}); diff --git a/lib/provider/spotify/album/tracks.dart b/lib/provider/spotify/album/tracks.dart deleted file mode 100644 index 13c48886..00000000 --- a/lib/provider/spotify/album/tracks.dart +++ /dev/null @@ -1,63 +0,0 @@ -part of '../spotify.dart'; - -class AlbumTracksState extends PaginatedState { - AlbumTracksState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - AlbumTracksState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return AlbumTracksState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class AlbumTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier { - AlbumTracksNotifier() : super(); - - @override - fetch(arg, offset, limit) async { - final tracks = await spotify.invoke( - (api) => api.albums.tracks(arg.id!).getPage(limit, offset), - ); - final items = await tracks.items!.asTracks(arg, ref); - - return ( - items: items, - hasMore: !tracks.isLast, - nextOffset: tracks.nextOffset, - ); - } - - @override - build(arg) async { - ref.cacheFor(); - - ref.watch(spotifyProvider); - final (:items, :nextOffset, :hasMore) = await fetch(arg, 0, 20); - return AlbumTracksState( - items: items, - offset: nextOffset, - limit: 20, - hasMore: hasMore, - ); - } -} - -final albumTracksProvider = AutoDisposeAsyncNotifierProviderFamily< - AlbumTracksNotifier, AlbumTracksState, AlbumSimple>( - () => AlbumTracksNotifier(), -); diff --git a/lib/provider/spotify/artist/albums.dart b/lib/provider/spotify/artist/albums.dart deleted file mode 100644 index 7852738a..00000000 --- a/lib/provider/spotify/artist/albums.dart +++ /dev/null @@ -1,68 +0,0 @@ -part of '../spotify.dart'; - -class ArtistAlbumsState extends PaginatedState { - ArtistAlbumsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - ArtistAlbumsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return ArtistAlbumsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class ArtistAlbumsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< - Album, ArtistAlbumsState, String> { - ArtistAlbumsNotifier() : super(); - - @override - fetch(arg, offset, limit) async { - final market = ref.read(userPreferencesProvider).market; - final albums = await spotify.invoke( - (api) => api.artists.albums(arg, country: market).getPage(limit, offset), - ); - - final items = albums.items?.toList() ?? []; - - return ( - items: items, - hasMore: !albums.isLast, - nextOffset: albums.nextOffset, - ); - } - - @override - build(arg) async { - ref.cacheFor(); - - ref.watch(spotifyProvider); - ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 20); - return ArtistAlbumsState( - items: items, - offset: nextOffset, - limit: 20, - hasMore: hasMore, - ); - } -} - -final artistAlbumsProvider = AutoDisposeAsyncNotifierProviderFamily< - ArtistAlbumsNotifier, ArtistAlbumsState, String>( - () => ArtistAlbumsNotifier(), -); diff --git a/lib/provider/spotify/artist/artist.dart b/lib/provider/spotify/artist/artist.dart deleted file mode 100644 index dfee03e9..00000000 --- a/lib/provider/spotify/artist/artist.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of '../spotify.dart'; - -final artistProvider = - FutureProvider.autoDispose.family((ref, String artistId) { - ref.cacheFor(); - - final spotify = ref.watch(spotifyProvider); - - return spotify.invoke((api) => api.artists.get(artistId)); -}); diff --git a/lib/provider/spotify/artist/following.dart b/lib/provider/spotify/artist/following.dart deleted file mode 100644 index 3a3795b7..00000000 --- a/lib/provider/spotify/artist/following.dart +++ /dev/null @@ -1,152 +0,0 @@ -part of '../spotify.dart'; - -class FollowedArtistsState extends CursorPaginatedState { - FollowedArtistsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - FollowedArtistsState copyWith({ - List? items, - String? offset, - int? limit, - bool? hasMore, - }) { - return FollowedArtistsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class FollowedArtistsNotifier - extends CursorPaginatedAsyncNotifier { - final Dio dio; - FollowedArtistsNotifier() - : dio = Dio(), - super(); - - @override - fetch(offset, limit) async { - final artists = await spotify.invoke( - (api) => api.me.following(FollowingType.artist).getPage( - limit, - offset ?? '', - ), - ); - - return (artists.items?.toList() ?? [], artists.after); - } - - @override - build() async { - ref.watch(spotifyProvider); - final (artists, nextCursor) = await fetch(null, 50); - return FollowedArtistsState( - items: artists, - offset: nextCursor, - limit: 50, - hasMore: artists.length == 50, - ); - } - - Future _followArtists(List artistIds) async { - try { - final creds = await spotify.invoke( - (api) => api.getCredentials(), - ); - - await dio.post( - "https://api-partner.spotify.com/pathfinder/v1/query", - data: { - "variables": { - "uris": artistIds.map((id) => "spotify:artist:$id").toList() - }, - "operationName": "addToLibrary", - "extensions": { - "persistedQuery": { - "version": 1, - "sha256Hash": - "a3c1ff58e6a36fec5fe1e3a193dc95d9071d96b9ba53c5ba9c1494fb1ee73915" - } - } - }, - options: Options( - headers: { - "accept": "application/json", - 'app-platform': 'WebPlayer', - 'authorization': 'Bearer ${creds.accessToken}', - 'content-type': 'application/json;charset=UTF-8', - }, - responseType: ResponseType.json, - ), - ); - } on DioException catch (e, stack) { - AppLogger.reportError(e, stack); - } - } - - Future saveArtists(List artistIds) async { - if (state.value == null) return; - // await spotify.me.follow(FollowingType.artist, artistIds); - await _followArtists(artistIds); - - state = await AsyncValue.guard(() async { - final artists = await spotify.invoke( - (api) => api.artists.list(artistIds), - ); - - return state.value!.copyWith( - items: [ - ...state.value!.items, - ...artists, - ], - ); - }); - - for (final id in artistIds) { - ref.invalidate(artistIsFollowingProvider(id)); - } - } - - Future removeArtists(List artistIds) async { - if (state.value == null) return; - await spotify.invoke( - (api) => api.me.unfollow(FollowingType.artist, artistIds), - ); - - state = await AsyncValue.guard(() async { - final artists = state.value!.items.where((artist) { - return !artistIds.contains(artist.id); - }).toList(); - - return state.value!.copyWith( - items: artists, - ); - }); - - for (final id in artistIds) { - ref.invalidate(artistIsFollowingProvider(id)); - } - } -} - -final followedArtistsProvider = - AsyncNotifierProvider( - () => FollowedArtistsNotifier(), -); - -final allFollowedArtistsProvider = FutureProvider>( - (ref) async { - final spotify = ref.watch(spotifyProvider); - final artists = await spotify.invoke( - (api) => api.me.following(FollowingType.artist).all(), - ); - return artists.toList(); - }, -); diff --git a/lib/provider/spotify/artist/is_following.dart b/lib/provider/spotify/artist/is_following.dart deleted file mode 100644 index fb519518..00000000 --- a/lib/provider/spotify/artist/is_following.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of '../spotify.dart'; - -final artistIsFollowingProvider = FutureProvider.family( - (ref, String artistId) async { - final spotify = ref.watch(spotifyProvider); - return spotify.invoke( - (api) => api.me.checkFollowing(FollowingType.artist, [artistId]).then( - (value) => value[artistId] ?? false, - ), - ); - }, -); diff --git a/lib/provider/spotify/artist/related.dart b/lib/provider/spotify/artist/related.dart deleted file mode 100644 index 7246fa11..00000000 --- a/lib/provider/spotify/artist/related.dart +++ /dev/null @@ -1,13 +0,0 @@ -part of '../spotify.dart'; - -final relatedArtistsProvider = FutureProvider.autoDispose - .family, String>((ref, artistId) async { - ref.cacheFor(); - - final spotify = ref.watch(spotifyProvider); - final artists = await spotify.invoke( - (api) => api.artists.relatedArtists(artistId), - ); - - return artists.toList(); -}); diff --git a/lib/provider/spotify/artist/top_tracks.dart b/lib/provider/spotify/artist/top_tracks.dart deleted file mode 100644 index 51321b21..00000000 --- a/lib/provider/spotify/artist/top_tracks.dart +++ /dev/null @@ -1,16 +0,0 @@ -part of '../spotify.dart'; - -final artistTopTracksProvider = - FutureProvider.autoDispose.family, String>( - (ref, artistId) async { - ref.cacheFor(); - - final spotify = ref.watch(spotifyProvider); - final market = ref.watch(userPreferencesProvider.select((s) => s.market)); - final tracks = await spotify.invoke( - (api) => api.artists.topTracks(artistId, market), - ); - - return tracks.toList(); - }, -); diff --git a/lib/provider/spotify/artist/wikipedia.dart b/lib/provider/spotify/artist/wikipedia.dart deleted file mode 100644 index 7f22d5f6..00000000 --- a/lib/provider/spotify/artist/wikipedia.dart +++ /dev/null @@ -1,12 +0,0 @@ -part of '../spotify.dart'; - -final artistWikipediaSummaryProvider = FutureProvider.autoDispose - .family((ref, artist) async { - final query = artist.name.replaceAll(" ", "_"); - final res = await wikipedia.pageContent.pageSummaryTitleGet(query); - - if (res?.type != "standard") { - return await wikipedia.pageContent.pageSummaryTitleGet("${query}_(singer)"); - } - return res; -}); diff --git a/lib/provider/spotify/category/categories.dart b/lib/provider/spotify/category/categories.dart deleted file mode 100644 index 67476f34..00000000 --- a/lib/provider/spotify/category/categories.dart +++ /dev/null @@ -1,21 +0,0 @@ -part of '../spotify.dart'; - -final categoriesProvider = FutureProvider( - (ref) async { - final spotify = ref.watch(spotifyProvider); - final market = ref.watch(userPreferencesProvider.select((s) => s.market)); - final locale = ref.watch(userPreferencesProvider.select((s) => s.locale)); - final categories = await spotify.invoke( - (api) => api.categories - .list( - country: market, - locale: Intl.canonicalizedLocale( - locale.toString(), - ), - ) - .all(), - ); - - return categories.toList()..shuffle(); - }, -); diff --git a/lib/provider/spotify/category/genres.dart b/lib/provider/spotify/category/genres.dart deleted file mode 100644 index b4b75b7b..00000000 --- a/lib/provider/spotify/category/genres.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of '../spotify.dart'; - -final categoryGenresProvider = FutureProvider>((ref) async { - final customSpotify = ref.watch(customSpotifyEndpointProvider); - return await customSpotify.listGenreSeeds(); -}); diff --git a/lib/provider/spotify/category/playlists.dart b/lib/provider/spotify/category/playlists.dart deleted file mode 100644 index 2afd8d97..00000000 --- a/lib/provider/spotify/category/playlists.dart +++ /dev/null @@ -1,73 +0,0 @@ -part of '../spotify.dart'; - -class CategoryPlaylistsState extends PaginatedState { - CategoryPlaylistsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - CategoryPlaylistsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return CategoryPlaylistsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class CategoryPlaylistsNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< - PlaylistSimple, CategoryPlaylistsState, String> { - CategoryPlaylistsNotifier() : super(); - - @override - fetch(arg, offset, limit) async { - final preferences = ref.read(userPreferencesProvider); - final playlists = await Pages( - spotify.api, - "v1/browse/categories/$arg/playlists?country=${preferences.market.name}&locale=${preferences.locale}", - (json) => json == null ? null : PlaylistSimple.fromJson(json), - 'playlists', - (json) => PlaylistsFeatured.fromJson(json), - ).getPage(limit, offset); - - final items = playlists.items?.nonNulls.toList() ?? []; - - return ( - items: items, - hasMore: !playlists.isLast, - nextOffset: playlists.nextOffset, - ); - } - - @override - build(arg) async { - ref.cacheFor(); - - ref.watch(spotifyProvider); - ref.watch(userPreferencesProvider.select((s) => s.locale)); - ref.watch(userPreferencesProvider.select((s) => s.market)); - - final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 8); - - return CategoryPlaylistsState( - items: items, - offset: nextOffset, - limit: 8, - hasMore: hasMore, - ); - } -} - -final categoryPlaylistsProvider = AutoDisposeAsyncNotifierProviderFamily< - CategoryPlaylistsNotifier, CategoryPlaylistsState, String>( - () => CategoryPlaylistsNotifier(), -); diff --git a/lib/provider/spotify/playlist/favorite.dart b/lib/provider/spotify/playlist/favorite.dart deleted file mode 100644 index 4df888ce..00000000 --- a/lib/provider/spotify/playlist/favorite.dart +++ /dev/null @@ -1,146 +0,0 @@ -part of '../spotify.dart'; - -class FavoritePlaylistsState extends PaginatedState { - FavoritePlaylistsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - FavoritePlaylistsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return FavoritePlaylistsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class FavoritePlaylistsNotifier - extends PaginatedAsyncNotifier { - FavoritePlaylistsNotifier() : super(); - - @override - fetch(int offset, int limit) async { - final playlists = await spotify.invoke( - (api) => api.playlists.me.getPage( - limit, - offset, - ), - ); - - return playlists.items?.toList() ?? []; - } - - @override - build() async { - ref.watch(spotifyProvider); - final playlists = await fetch(0, 20); - - return FavoritePlaylistsState( - items: playlists, - offset: 0, - limit: 20, - hasMore: playlists.length == 20, - ); - } - - void updatePlaylist(PlaylistSimple playlist) { - if (state.value == null) return; - - if (state.value!.items.none((e) => e.id == playlist.id)) return; - - state = AsyncData( - state.value!.copyWith( - items: state.value!.items - .map((element) => element.id == playlist.id ? playlist : element) - .toList(), - ), - ); - } - - Future addFavorite(PlaylistSimple playlist) async { - await update((state) async { - await spotify.invoke( - (api) => api.playlists.followPlaylist(playlist.id!), - ); - return state.copyWith( - items: [...state.items, playlist], - ); - }); - - ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); - } - - Future removeFavorite(PlaylistSimple playlist) async { - await update((state) async { - await spotify.invoke( - (api) => api.playlists.unfollowPlaylist(playlist.id!), - ); - return state.copyWith( - items: state.items.where((e) => e.id != playlist.id).toList(), - ); - }); - - ref.invalidate(isFavoritePlaylistProvider(playlist.id!)); - } - - Future addTracks(String playlistId, List trackIds) async { - if (state.value == null) return; - - final spotify = ref.read(spotifyProvider); - - await spotify.invoke( - (api) => api.playlists.addTracks( - trackIds.map((id) => 'spotify:track:$id').toList(), - playlistId, - ), - ); - - ref.invalidate(playlistTracksProvider(playlistId)); - } - - Future removeTracks(String playlistId, List trackIds) async { - if (state.value == null) return; - - final spotify = ref.read(spotifyProvider); - - await spotify.invoke( - (api) => api.playlists.removeTracks( - trackIds.map((id) => 'spotify:track:$id').toList(), - playlistId, - ), - ); - - ref.invalidate(playlistTracksProvider(playlistId)); - } -} - -final favoritePlaylistsProvider = - AsyncNotifierProvider( - () => FavoritePlaylistsNotifier(), -); - -final isFavoritePlaylistProvider = FutureProvider.family( - (ref, id) async { - final spotify = ref.watch(spotifyProvider); - final me = ref.watch(meProvider); - - if (me.value == null) { - return false; - } - - final follows = await spotify - .invoke((api) => api.playlists.followedByUsers(id, [me.value!.id!])); - - return follows[me.value!.id!] ?? false; - }, -); diff --git a/lib/provider/spotify/playlist/featured.dart b/lib/provider/spotify/playlist/featured.dart deleted file mode 100644 index 9f751909..00000000 --- a/lib/provider/spotify/playlist/featured.dart +++ /dev/null @@ -1,57 +0,0 @@ -part of '../spotify.dart'; - -class FeaturedPlaylistsState extends PaginatedState { - FeaturedPlaylistsState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - FeaturedPlaylistsState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return FeaturedPlaylistsState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class FeaturedPlaylistsNotifier - extends PaginatedAsyncNotifier { - FeaturedPlaylistsNotifier() : super(); - - @override - fetch(int offset, int limit) async { - final playlists = await spotify.invoke( - (api) => api.playlists.featured.getPage(limit, offset), - ); - - return playlists.items?.toList() ?? []; - } - - @override - build() async { - ref.watch(spotifyProvider); - final playlists = await fetch(0, 20); - - return FeaturedPlaylistsState( - items: playlists, - offset: 0, - limit: 20, - hasMore: playlists.length == 20, - ); - } -} - -final featuredPlaylistsProvider = - AsyncNotifierProvider( - () => FeaturedPlaylistsNotifier(), -); diff --git a/lib/provider/spotify/playlist/generate.dart b/lib/provider/spotify/playlist/generate.dart deleted file mode 100644 index b2250df6..00000000 --- a/lib/provider/spotify/playlist/generate.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of '../spotify.dart'; - -final generatePlaylistProvider = FutureProvider.autoDispose - .family, GeneratePlaylistProviderInput>( - (ref, input) async { - final spotify = ref.watch(spotifyProvider); - final market = ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - - final recommendation = await spotify.invoke( - (api) => api.recommendations - .get( - limit: input.limit, - seedArtists: input.seedArtists?.toList(), - seedGenres: input.seedGenres?.toList(), - seedTracks: input.seedTracks?.toList(), - market: market, - max: (input.max?.toJson()?..removeWhere((key, value) => value == null)) - ?.cast(), - min: (input.min?.toJson()?..removeWhere((key, value) => value == null)) - ?.cast(), - target: (input.target?.toJson() - ?..removeWhere((key, value) => value == null)) - ?.cast(), - ) - .catchError((e, stackTrace) { - AppLogger.reportError(e, stackTrace); - return Recommendations(); - }), - ); - - if (recommendation.tracks?.isEmpty ?? true) { - return []; - } - - final tracks = await spotify.invoke( - (api) => - api.tracks.list(recommendation.tracks!.map((e) => e.id!).toList()), - ); - - return tracks.toList(); - }, -); diff --git a/lib/provider/spotify/playlist/liked.dart b/lib/provider/spotify/playlist/liked.dart deleted file mode 100644 index 99c75719..00000000 --- a/lib/provider/spotify/playlist/liked.dart +++ /dev/null @@ -1,39 +0,0 @@ -part of '../spotify.dart'; - -class LikedTracksNotifier extends AsyncNotifier> { - @override - FutureOr> build() async { - final spotify = ref.watch(spotifyProvider); - final savedTracked = await spotify.invoke( - (api) => api.tracks.me.saved.all(), - ); - - return savedTracked.map((e) => e.track!).toList(); - } - - Future toggleFavorite(Track track) async { - if (state.value == null) return; - final spotify = ref.read(spotifyProvider); - - await update((tracks) async { - final isLiked = tracks.map((e) => e.id).contains(track.id); - - if (isLiked) { - await spotify.invoke( - (api) => api.tracks.me.removeOne(track.id!), - ); - return tracks.where((e) => e.id != track.id).toList(); - } else { - await spotify.invoke( - (api) => api.tracks.me.saveOne(track.id!), - ); - return [track, ...tracks]; - } - }); - } -} - -final likedTracksProvider = - AsyncNotifierProvider>( - () => LikedTracksNotifier(), -); diff --git a/lib/provider/spotify/playlist/playlist.dart b/lib/provider/spotify/playlist/playlist.dart deleted file mode 100644 index 34d1fe8e..00000000 --- a/lib/provider/spotify/playlist/playlist.dart +++ /dev/null @@ -1,173 +0,0 @@ -part of '../spotify.dart'; - -typedef PlaylistInput = ({ - String playlistName, - bool? public, - bool? collaborative, - String? description, - String? base64Image, -}); - -class PlaylistNotifier extends FamilyAsyncNotifier { - @override - FutureOr build(String arg) { - final spotify = ref.watch(spotifyProvider); - return spotify.invoke( - (api) => api.playlists.get(arg), - ); - } - - Future create(PlaylistInput input, [ValueChanged? onError]) async { - if (state is AsyncLoading) return; - state = const AsyncLoading(); - - final spotify = ref.read(spotifyProvider); - final me = ref.read(meProvider); - - if (me.value == null) return; - - state = await AsyncValue.guard(() async { - try { - final playlist = await spotify.invoke( - (api) => api.playlists.createPlaylist( - me.value!.id!, - input.playlistName, - collaborative: input.collaborative, - description: input.description, - public: input.public, - ), - ); - - if (input.base64Image != null) { - await spotify.invoke( - (api) => api.playlists.updatePlaylistImage( - playlist.id!, - input.base64Image!, - ), - ); - } - - return playlist; - } catch (e) { - onError?.call(e); - rethrow; - } - }); - - ref.invalidate(favoritePlaylistsProvider); - } - - Future modify(PlaylistInput input, [ValueChanged? onError]) async { - if (state.value == null) return; - - final spotify = ref.read(spotifyProvider); - - await update((state) async { - try { - await spotify.invoke( - (api) => api.playlists.updatePlaylist( - state.id!, - input.playlistName, - collaborative: input.collaborative, - description: input.description, - public: input.public, - ), - ); - - if (input.base64Image != null) { - await spotify.invoke( - (api) => api.playlists.updatePlaylistImage( - state.id!, - input.base64Image!, - ), - ); - - final playlist = await spotify.invoke( - (api) => api.playlists.get(state.id!), - ); - - ref.read(favoritePlaylistsProvider.notifier).updatePlaylist(playlist); - return playlist; - } - - final playlist = Playlist.fromJson( - { - ...state.toJson(), - 'name': input.playlistName, - 'collaborative': input.collaborative, - 'description': input.description, - 'public': input.public, - }, - ); - - ref.read(favoritePlaylistsProvider.notifier).updatePlaylist(playlist); - - return playlist; - } catch (e, stack) { - onError?.call(e); - AppLogger.reportError(e, stack); - rethrow; - } - }); - } - - Future addTracks(List trackIds, [ValueChanged? onError]) async { - try { - if (state.value == null) return; - - final spotify = ref.read(spotifyProvider); - - await spotify.invoke( - (api) => api.playlists.addTracks( - trackIds.map((id) => "spotify:track:$id").toList(), - state.value!.id!, - ), - ); - } catch (e, stack) { - onError?.call(e); - AppLogger.reportError(e, stack); - rethrow; - } - } -} - -final playlistProvider = - AsyncNotifierProvider.family( - () => PlaylistNotifier(), -); - -final _blendModes = BlendMode.values - .where((e) => switch (e) { - BlendMode.clear || - BlendMode.src || - BlendMode.srcATop || - BlendMode.srcIn || - BlendMode.srcOut || - BlendMode.srcOver || - BlendMode.dstOut || - BlendMode.xor => - false, - _ => true - }) - .toList(); - -typedef PlaylistImageInfo = ({ - Color color, - BlendMode colorBlendMode, - String src, - Alignment placement, -}); - -final playlistImageProvider = Provider.family( - (ref, playlistId) { - final random = Random(); - - return ( - color: Colors.primaries[random.nextInt(Colors.primaries.length)], - colorBlendMode: _blendModes[random.nextInt(_blendModes.length)], - src: Assets - .patterns.values[random.nextInt(Assets.patterns.values.length)].path, - placement: random.nextBool() ? Alignment.topLeft : Alignment.bottomLeft, - ); - }, -); diff --git a/lib/provider/spotify/playlist/tracks.dart b/lib/provider/spotify/playlist/tracks.dart deleted file mode 100644 index 1dbb83be..00000000 --- a/lib/provider/spotify/playlist/tracks.dart +++ /dev/null @@ -1,70 +0,0 @@ -part of '../spotify.dart'; - -class PlaylistTracksState extends PaginatedState { - PlaylistTracksState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - PlaylistTracksState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return PlaylistTracksState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class PlaylistTracksNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier< - Track, PlaylistTracksState, String> { - PlaylistTracksNotifier() : super(); - - @override - fetch(arg, offset, limit) async { - final tracks = await spotify.invoke( - (api) => api.playlists.getTracksByPlaylistId(arg).getPage(limit, offset), - ); - - /// Filter out tracks with null id because some personal playlists - /// may contain local tracks that are not available in the Spotify catalog - final items = tracks.items - ?.where((track) => track.id != null && track.type == "track") - .toList() ?? - []; - - return ( - items: items, - hasMore: !tracks.isLast, - nextOffset: tracks.nextOffset, - ); - } - - @override - build(arg) async { - ref.cacheFor(); - - ref.watch(spotifyProvider); - final (items: tracks, :hasMore, :nextOffset) = await fetch(arg, 0, 20); - - return PlaylistTracksState( - items: tracks, - offset: nextOffset, - limit: 20, - hasMore: hasMore, - ); - } -} - -final playlistTracksProvider = AutoDisposeAsyncNotifierProviderFamily< - PlaylistTracksNotifier, PlaylistTracksState, String>( - () => PlaylistTracksNotifier(), -); diff --git a/lib/provider/spotify/search/search.dart b/lib/provider/spotify/search/search.dart deleted file mode 100644 index 828cc382..00000000 --- a/lib/provider/spotify/search/search.dart +++ /dev/null @@ -1,90 +0,0 @@ -part of '../spotify.dart'; - -final searchTermStateProvider = StateProvider.autoDispose( - (ref) { - ref.cacheFor(const Duration(minutes: 2)); - return ""; - }, -); - -class SearchState extends PaginatedState { - SearchState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - SearchState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }) { - return SearchState( - items: items ?? this.items, - offset: offset ?? this.offset, - limit: limit ?? this.limit, - hasMore: hasMore ?? this.hasMore, - ); - } -} - -class SearchNotifier extends AutoDisposeFamilyPaginatedAsyncNotifier, SearchType> { - SearchNotifier() : super(); - - @override - fetch(arg, offset, limit) async { - if (state.value == null) { - return ( - items: [], - hasMore: false, - nextOffset: 0, - ); - } - final results = await spotify.invoke( - (api) => api.search - .get( - ref.read(searchTermStateProvider), - types: [arg], - market: ref.read(userPreferencesProvider).market, - ) - .getPage(limit, offset), - ); - - final items = results.expand((e) => e.items ?? []).toList().cast(); - - return ( - items: items, - hasMore: items.length == limit, - nextOffset: offset + limit, - ); - } - - @override - build(arg) async { - ref.cacheFor(const Duration(minutes: 2)); - - ref.watch(searchTermStateProvider); - ref.watch(spotifyProvider); - ref.watch( - userPreferencesProvider.select((value) => value.market), - ); - - final (:items, :hasMore, :nextOffset) = await fetch(arg, 0, 10); - - return SearchState( - items: items, - offset: nextOffset, - limit: 10, - hasMore: hasMore, - ); - } -} - -final searchProvider = AsyncNotifierProvider.autoDispose - .family( - () => SearchNotifier(), -); diff --git a/lib/provider/spotify/spotify.dart b/lib/provider/spotify/spotify.dart deleted file mode 100644 index d954bb8d..00000000 --- a/lib/provider/spotify/spotify.dart +++ /dev/null @@ -1,134 +0,0 @@ -library spotify; - -import 'dart:async'; -import 'dart:math'; - -import 'package:drift/drift.dart'; -import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/collections/env.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:collection/collection.dart'; -import 'package:dio/dio.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:intl/intl.dart'; -import 'package:lrc/lrc.dart'; -import 'package:package_info_plus/package_info_plus.dart'; -import 'package:spotify/spotify.dart'; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -// ignore: depend_on_referenced_packages, implementation_imports -import 'package:riverpod/src/async_notifier.dart'; -import 'package:spotube/extensions/album_simple.dart'; -import 'package:spotube/extensions/track.dart'; -import 'package:spotube/models/lyrics.dart'; -import 'package:spotube/models/spotify/recommendation_seeds.dart'; -import 'package:spotube/models/spotify_friends.dart'; -import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; -import 'package:spotube/services/dio/dio.dart'; -import 'package:spotube/services/wikipedia/wikipedia.dart'; -import 'package:spotube/utils/primitive_utils.dart'; - -import 'package:wikipedia_api/wikipedia_api.dart'; - -part 'album/favorite.dart'; -part 'album/tracks.dart'; -part 'album/releases.dart'; -part 'album/is_saved.dart'; - -part 'artist/artist.dart'; -part 'artist/is_following.dart'; -part 'artist/following.dart'; -part 'artist/top_tracks.dart'; -part 'artist/albums.dart'; -part 'artist/wikipedia.dart'; -part 'artist/related.dart'; - -part 'category/genres.dart'; -part 'category/categories.dart'; -part 'category/playlists.dart'; - -part 'lyrics/synced.dart'; - -part 'playlist/favorite.dart'; -part 'playlist/playlist.dart'; -part 'playlist/liked.dart'; -part 'playlist/tracks.dart'; -part 'playlist/featured.dart'; -part 'playlist/generate.dart'; - -part 'search/search.dart'; - -part 'user/me.dart'; -part 'user/friends.dart'; - -part 'tracks/track.dart'; - -part 'views/view.dart'; - -part 'utils/mixin.dart'; -part 'utils/state.dart'; -part 'utils/provider.dart'; -part 'utils/async.dart'; - -part 'utils/provider/paginated.dart'; -part 'utils/provider/cursor.dart'; -part 'utils/provider/paginated_family.dart'; -part 'utils/provider/cursor_family.dart'; - -class SpotifyApiWrapper { - final SpotifyApi api; - - final Ref ref; - SpotifyApiWrapper( - this.ref, - this.api, - ); - - bool _isRefreshing = false; - - FutureOr invoke( - FutureOr Function(SpotifyApi api) fn, - ) async { - try { - return await fn(api); - } catch (e) { - if (((e is AuthorizationException && e.error == 'invalid_token') || - e is ExpirationException) && - !_isRefreshing) { - _isRefreshing = true; - await ref.read(authenticationProvider.notifier).refreshCredentials(); - - _isRefreshing = false; - return await fn(api); - } - rethrow; - } - } -} - -final spotifyProvider = Provider( - (ref) { - final authState = ref.watch(authenticationProvider); - final anonCred = PrimitiveUtils.getRandomElement(Env.spotifySecrets); - - final wrapper = SpotifyApiWrapper( - ref, - authState.asData?.value == null - ? SpotifyApi( - SpotifyApiCredentials( - anonCred["clientId"], - anonCred["clientSecret"], - ), - ) - : SpotifyApi.withAccessToken( - authState.asData!.value!.accessToken.value, - ), - ); - - return wrapper; - }, -); diff --git a/lib/provider/spotify/tracks/track.dart b/lib/provider/spotify/tracks/track.dart deleted file mode 100644 index 9863aa25..00000000 --- a/lib/provider/spotify/tracks/track.dart +++ /dev/null @@ -1,10 +0,0 @@ -part of '../spotify.dart'; - -final trackProvider = - FutureProvider.autoDispose.family((ref, id) async { - ref.cacheFor(); - - final spotify = ref.watch(spotifyProvider); - - return spotify.invoke((api) => api.tracks.get(id)); -}); diff --git a/lib/provider/spotify/user/friends.dart b/lib/provider/spotify/user/friends.dart deleted file mode 100644 index b9cc0f46..00000000 --- a/lib/provider/spotify/user/friends.dart +++ /dev/null @@ -1,7 +0,0 @@ -part of '../spotify.dart'; - -final friendsProvider = FutureProvider((ref) async { - final customSpotify = ref.watch(customSpotifyEndpointProvider); - - return customSpotify.getFriendActivity(); -}); diff --git a/lib/provider/spotify/user/me.dart b/lib/provider/spotify/user/me.dart deleted file mode 100644 index 09f5fc2d..00000000 --- a/lib/provider/spotify/user/me.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of '../spotify.dart'; - -final meProvider = FutureProvider((ref) async { - final spotify = ref.watch(spotifyProvider); - return spotify.invoke((api) => api.me.get()); -}); diff --git a/lib/provider/spotify/utils/async.dart b/lib/provider/spotify/utils/async.dart deleted file mode 100644 index 1040d682..00000000 --- a/lib/provider/spotify/utils/async.dart +++ /dev/null @@ -1,5 +0,0 @@ -part of '../spotify.dart'; - -extension PaginationExtension on AsyncValue { - bool get isLoadingNextPage => this is AsyncData && this is AsyncLoadingNext; -} diff --git a/lib/provider/spotify/utils/json_cast.dart b/lib/provider/spotify/utils/json_cast.dart deleted file mode 100644 index 30700971..00000000 --- a/lib/provider/spotify/utils/json_cast.dart +++ /dev/null @@ -1,21 +0,0 @@ -Map castNestedJson(Map map) { - return Map.castFrom( - map.map((key, value) { - if (value is Map) { - return MapEntry( - key, - castNestedJson(value), - ); - } else if (value is Iterable) { - return MapEntry( - key, - value.map((e) { - if (e is Map) return castNestedJson(e); - return e; - }).toList(), - ); - } - return MapEntry(key, value); - }), - ); -} diff --git a/lib/provider/spotify/utils/mixin.dart b/lib/provider/spotify/utils/mixin.dart deleted file mode 100644 index 60788814..00000000 --- a/lib/provider/spotify/utils/mixin.dart +++ /dev/null @@ -1,24 +0,0 @@ -part of '../spotify.dart'; - -// ignore: invalid_use_of_internal_member -mixin SpotifyMixin on AsyncNotifierBase { - SpotifyApiWrapper get spotify => ref.read(spotifyProvider); -} - -extension on AutoDisposeAsyncNotifierProviderRef { - // When invoked keeps your provider alive for [duration] - void cacheFor([Duration duration = const Duration(minutes: 5)]) { - final link = keepAlive(); - final timer = Timer(duration, () => link.close()); - onDispose(() => timer.cancel()); - } -} - -extension on AutoDisposeRef { - // When invoked keeps your provider alive for [duration] - void cacheFor([Duration duration = const Duration(minutes: 5)]) { - final link = keepAlive(); - final timer = Timer(duration, () => link.close()); - onDispose(() => timer.cancel()); - } -} diff --git a/lib/provider/spotify/utils/provider.dart b/lib/provider/spotify/utils/provider.dart deleted file mode 100644 index 50458c3a..00000000 --- a/lib/provider/spotify/utils/provider.dart +++ /dev/null @@ -1,6 +0,0 @@ -part of '../spotify.dart'; - -// ignore: subtype_of_sealed_class -class AsyncLoadingNext extends AsyncData { - const AsyncLoadingNext(super.value); -} diff --git a/lib/provider/spotify/utils/provider/cursor.dart b/lib/provider/spotify/utils/provider/cursor.dart deleted file mode 100644 index c241827e..00000000 --- a/lib/provider/spotify/utils/provider/cursor.dart +++ /dev/null @@ -1,56 +0,0 @@ -part of '../../spotify.dart'; - -mixin CursorPaginatedAsyncNotifierMixin> - // ignore: invalid_use_of_internal_member - on AsyncNotifierBase { - Future<(List items, String nextCursor)> fetch(String? offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch(state.value!.offset, state.value!.limit); - return state.value!.copyWith( - hasMore: items.$1.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items.$1, - ], - offset: items.$2, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch(state.offset, state.limit); - - hasMore = items.$1.length == state.limit; - return state.copyWith( - items: [...state.items, ...items.$1], - offset: items.$2, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class CursorPaginatedAsyncNotifier> extends AsyncNotifier - with CursorPaginatedAsyncNotifierMixin, SpotifyMixin {} - -abstract class AutoDisposeCursorPaginatedAsyncNotifier> extends AutoDisposeAsyncNotifier - with CursorPaginatedAsyncNotifierMixin, SpotifyMixin {} diff --git a/lib/provider/spotify/utils/provider/cursor_family.dart b/lib/provider/spotify/utils/provider/cursor_family.dart deleted file mode 100644 index ea8577de..00000000 --- a/lib/provider/spotify/utils/provider/cursor_family.dart +++ /dev/null @@ -1,113 +0,0 @@ -part of '../../spotify.dart'; - -abstract class FamilyCursorPaginatedAsyncNotifier< - K, - T extends CursorPaginatedState, - A> extends FamilyAsyncNotifier with SpotifyMixin { - Future<(List items, String nextCursor)> fetch( - A arg, - String? offset, - int limit, - ); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch(arg, state.value!.offset, state.value!.limit); - return state.value!.copyWith( - hasMore: items.$1.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items.$1, - ], - offset: items.$2, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - arg, - state.offset, - state.limit, - ); - - hasMore = items.$1.length == state.limit; - return state.copyWith( - items: [...state.items, ...items.$1], - offset: items.$2, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class AutoDisposeFamilyCursorPaginatedAsyncNotifier< - K, - T extends CursorPaginatedState, - A> extends AutoDisposeFamilyAsyncNotifier with SpotifyMixin { - Future<(List items, String nextCursor)> fetch( - A arg, - String? offset, - int limit, - ); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch(arg, state.value!.offset, state.value!.limit); - return state.value!.copyWith( - hasMore: items.$1.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items.$1, - ], - offset: items.$2, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - arg, - state.offset, - state.limit, - ); - - hasMore = items.$1.length == state.limit; - return state.copyWith( - items: [...state.items, ...items.$1], - offset: items.$2, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} diff --git a/lib/provider/spotify/utils/provider/paginated.dart b/lib/provider/spotify/utils/provider/paginated.dart deleted file mode 100644 index 30b66e67..00000000 --- a/lib/provider/spotify/utils/provider/paginated.dart +++ /dev/null @@ -1,63 +0,0 @@ -part of '../../spotify.dart'; - -mixin PaginatedAsyncNotifierMixin> - // ignore: invalid_use_of_internal_member - on AsyncNotifierBase { - Future> fetch(int offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final items = await fetch( - state.value!.offset + state.value!.limit, - state.value!.limit, - ); - return state.value!.copyWith( - hasMore: items.length == state.value!.limit, - items: [ - ...state.value!.items, - ...items, - ], - offset: state.value!.offset + state.value!.limit, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final items = await fetch( - state.offset + state.limit, - state.limit, - ); - - hasMore = items.length == state.limit; - return state.copyWith( - items: [...state.items, ...items], - offset: state.offset + state.limit, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class PaginatedAsyncNotifier> - extends AsyncNotifier - with PaginatedAsyncNotifierMixin, SpotifyMixin {} - -abstract class AutoDisposePaginatedAsyncNotifier> - extends AutoDisposeAsyncNotifier - with PaginatedAsyncNotifierMixin, SpotifyMixin {} diff --git a/lib/provider/spotify/utils/provider/paginated_family.dart b/lib/provider/spotify/utils/provider/paginated_family.dart deleted file mode 100644 index c08c8673..00000000 --- a/lib/provider/spotify/utils/provider/paginated_family.dart +++ /dev/null @@ -1,120 +0,0 @@ -part of '../../spotify.dart'; - -typedef PseudoPaginatedProps = ({ - List items, - int nextOffset, - bool hasMore, -}); - -abstract class FamilyPaginatedAsyncNotifier< - K, - T extends BasePaginatedState, - A> extends FamilyAsyncNotifier with SpotifyMixin { - Future> fetch(A arg, int offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final (:items, :hasMore, :nextOffset) = await fetch( - arg, - state.value!.offset, - state.value!.limit, - ); - return state.value!.copyWith( - hasMore: hasMore, - items: [ - ...state.value!.items, - ...items, - ], - offset: nextOffset, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final res = await fetch( - arg, - state.offset, - state.limit, - ); - - hasMore = res.hasMore; - return state.copyWith( - items: [...state.items, ...res.items], - offset: res.nextOffset, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} - -abstract class AutoDisposeFamilyPaginatedAsyncNotifier< - K, - T extends BasePaginatedState, - A> extends AutoDisposeFamilyAsyncNotifier with SpotifyMixin { - Future> fetch(A arg, int offset, int limit); - - Future fetchMore() async { - if (state.value == null || !state.value!.hasMore) return; - - state = AsyncLoadingNext(state.asData!.value); - - state = await AsyncValue.guard( - () async { - final (:items, :hasMore, :nextOffset) = await fetch( - arg, - state.value!.offset, - state.value!.limit, - ); - - return state.value!.copyWith( - hasMore: hasMore, - items: [ - ...state.value!.items, - ...items, - ], - offset: nextOffset, - ) as T; - }, - ); - } - - Future> fetchAll() async { - if (state.value == null) return []; - if (!state.value!.hasMore) return state.value!.items; - - bool hasMore = true; - while (hasMore) { - await update((state) async { - final res = await fetch( - arg, - state.offset, - state.limit, - ); - - hasMore = res.hasMore; - return state.copyWith( - items: [...state.items, ...res.items], - offset: res.nextOffset, - hasMore: hasMore, - ) as T; - }); - } - - return state.value!.items; - } -} diff --git a/lib/provider/spotify/utils/state.dart b/lib/provider/spotify/utils/state.dart deleted file mode 100644 index 4b79ac7d..00000000 --- a/lib/provider/spotify/utils/state.dart +++ /dev/null @@ -1,56 +0,0 @@ -part of '../spotify.dart'; - -abstract class BasePaginatedState { - final List items; - final Cursor offset; - final int limit; - final bool hasMore; - - BasePaginatedState({ - required this.items, - required this.offset, - required this.limit, - required this.hasMore, - }); - - BasePaginatedState copyWith({ - List? items, - Cursor? offset, - int? limit, - bool? hasMore, - }); -} - -abstract class PaginatedState extends BasePaginatedState { - PaginatedState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - PaginatedState copyWith({ - List? items, - int? offset, - int? limit, - bool? hasMore, - }); -} - -abstract class CursorPaginatedState extends BasePaginatedState { - CursorPaginatedState({ - required super.items, - required super.offset, - required super.limit, - required super.hasMore, - }); - - @override - CursorPaginatedState copyWith({ - List? items, - String? offset, - int? limit, - bool? hasMore, - }); -} diff --git a/lib/provider/spotify/views/home.dart b/lib/provider/spotify/views/home.dart deleted file mode 100644 index 87c049f9..00000000 --- a/lib/provider/spotify/views/home.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; - -final homeViewProvider = FutureProvider((ref) async { - final country = ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - final spTCookie = ref.watch( - authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")), - ); - - final spotify = ref.watch(customSpotifyEndpointProvider); - - return spotify.getHomeFeed( - country: country, - spTCookie: spTCookie, - ); -}); diff --git a/lib/provider/spotify/views/home_section.dart b/lib/provider/spotify/views/home_section.dart deleted file mode 100644 index 13f547e1..00000000 --- a/lib/provider/spotify/views/home_section.dart +++ /dev/null @@ -1,24 +0,0 @@ -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/models/spotify/home_feed.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/provider/custom_spotify_endpoint_provider.dart'; -import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; - -final homeSectionViewProvider = - FutureProvider.family( - (ref, sectionUri) async { - final country = ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - final spTCookie = ref.watch( - authenticationProvider.select((s) => s.asData?.value?.getCookie("sp_t")), - ); - - final spotify = ref.watch(customSpotifyEndpointProvider); - - return spotify.getHomeFeedSection( - sectionUri, - country: country, - spTCookie: spTCookie, - ); -}); diff --git a/lib/provider/spotify/views/view.dart b/lib/provider/spotify/views/view.dart deleted file mode 100644 index ff565feb..00000000 --- a/lib/provider/spotify/views/view.dart +++ /dev/null @@ -1,19 +0,0 @@ -part of '../spotify.dart'; - -final viewProvider = FutureProvider.family, String>( - (ref, viewName) async { - final customSpotify = ref.watch(customSpotifyEndpointProvider); - final market = ref.watch( - userPreferencesProvider.select((s) => s.market), - ); - final locale = ref.watch( - userPreferencesProvider.select((s) => s.locale), - ); - - return customSpotify.getView( - viewName, - market: market, - locale: Intl.canonicalizedLocale(locale.toString()), - ); - }, -); diff --git a/lib/provider/user_preferences/user_preferences_provider.dart b/lib/provider/user_preferences/user_preferences_provider.dart index 30ee8b3f..a5be97e2 100644 --- a/lib/provider/user_preferences/user_preferences_provider.dart +++ b/lib/provider/user_preferences/user_preferences_provider.dart @@ -4,8 +4,8 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:path/path.dart'; import 'package:path_provider/path_provider.dart' as paths; import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; -import 'package:spotify/spotify.dart'; import 'package:spotube/models/database/database.dart'; +import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/modules/settings/color_scheme_picker_dialog.dart'; import 'package:spotube/provider/database/database.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index ead81967..4deaf720 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -5,7 +5,6 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter/foundation.dart'; -import 'package:spotube/models/local_track.dart'; import 'package:spotube/services/audio_player/custom_player.dart'; import 'dart:async'; @@ -54,7 +53,7 @@ class SpotubeMedia extends mk.Media { return switch (track) { /// [super.uri] must be used instead of [track.path] to prevent wrong /// path format exceptions in Windows causing [extras] to be null - LocalTrack() => super.uri, + SpotubeLocalTrackObject() => super.uri, _ => "http://$_host:" "$serverPort/stream/${track.id}", }; diff --git a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart b/lib/services/custom_spotify_endpoints/spotify_endpoints.dart deleted file mode 100644 index c05095b3..00000000 --- a/lib/services/custom_spotify_endpoints/spotify_endpoints.dart +++ /dev/null @@ -1,222 +0,0 @@ -import 'dart:convert'; - -import 'package:dio/dio.dart'; -import 'package:spotify/spotify.dart'; -import 'package:spotube/models/spotify/home_feed.dart'; -import 'package:spotube/models/spotify_friends.dart'; -import 'package:timezone/timezone.dart' as tz; - -class CustomSpotifyEndpoints { - static const _baseUrl = 'https://api.spotify.com/v1'; - final String accessToken; - final Dio _client; - - CustomSpotifyEndpoints(this.accessToken) - : _client = Dio( - BaseOptions( - baseUrl: _baseUrl, - responseType: ResponseType.json, - headers: { - "content-type": "application/json", - if (accessToken.isNotEmpty) - "authorization": "Bearer $accessToken", - "accept": "application/json", - }, - ), - ); - - // views API - - /// Get a single view of given genre - /// - /// Currently known genres are: - /// - new-releases-page - /// - made-for-x-hub (it requires authentication) - /// - my-mix-genres (it requires authentication) - /// - artist-seed-mixes (it requires authentication) - /// - my-mix-decades (it requires authentication) - /// - my-mix-moods (it requires authentication) - /// - podcasts-and-more (it requires authentication) - /// - uniquely-yours-in-hub (it requires authentication) - /// - made-for-x-dailymix (it requires authentication) - /// - made-for-x-discovery (it requires authentication) - Future> getView( - String view, { - int limit = 20, - int contentLimit = 10, - List types = const [ - "album", - "playlist", - "artist", - "show", - "station", - "episode", - "merch", - "artist_concerts", - "uri_link" - ], - String imageStyle = "gradient_overlay", - String includeExternal = "audio", - String? locale, - Market? market, - Market? country, - }) async { - if (accessToken.isEmpty) { - throw Exception('[CustomSpotifyEndpoints.getView]: accessToken is empty'); - } - - final queryParams = { - 'limit': limit.toString(), - 'content_limit': contentLimit.toString(), - 'types': types.join(','), - 'image_style': imageStyle, - 'include_external': includeExternal, - 'timestamp': DateTime.now().toUtc().toIso8601String(), - if (locale != null) 'locale': locale, - if (market != null) 'market': market.name, - if (country != null) 'country': country.name, - }.entries.map((e) => '${e.key}=${e.value}').join('&'); - - final res = await _client.getUri( - Uri.parse('$_baseUrl/views/$view?$queryParams'), - ); - - if (res.statusCode == 200) { - return res.data; - } else { - throw Exception( - '[CustomSpotifyEndpoints.getView]: Failed to get view' - '\nStatus code: ${res.statusCode}' - '\nBody: ${res.data}', - ); - } - } - - Future> listGenreSeeds() async { - final res = await _client.getUri( - Uri.parse("$_baseUrl/recommendations/available-genre-seeds"), - ); - - if (res.statusCode == 200) { - final body = res.data; - return List.from(body["genres"] ?? []); - } else { - throw Exception( - '[CustomSpotifyEndpoints.listGenreSeeds]: Failed to get genre seeds' - '\nStatus code: ${res.statusCode}' - '\nBody: ${res.data}', - ); - } - } - - Future getFriendActivity() async { - final res = await _client.getUri( - Uri.parse("https://guc-spclient.spotify.com/presence-view/v1/buddylist"), - ); - return SpotifyFriends.fromJson(res.data); - } - - Future getHomeFeed({ - required Market country, - String? spTCookie, - }) async { - final headers = { - 'app-platform': 'WebPlayer', - 'authorization': 'Bearer $accessToken', - 'content-type': 'application/json;charset=UTF-8', - 'dnt': '1', - 'origin': 'https://open.spotify.com', - 'referer': 'https://open.spotify.com/' - }; - final response = await _client.getUri( - Uri( - scheme: "https", - host: "api-partner.spotify.com", - path: "/pathfinder/v1/query", - queryParameters: { - "operationName": "home", - "variables": jsonEncode({ - "timeZone": tz.local.name, - "sp_t": spTCookie ?? "", - "country": country.name, - "facet": null, - "sectionItemsLimit": 10 - }), - "extensions": jsonEncode( - { - "persistedQuery": { - "version": 1, - - /// GraphQL persisted Query hash - /// This can change overtime. We've to lookout for it - /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/ - "sha256Hash": - "eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be", - } - }, - ), - }, - ), - options: Options(headers: headers), - ); - - final data = SpotifyHomeFeed.fromJson( - transformHomeFeedJsonMap(response.data), - ); - - return data; - } - - Future getHomeFeedSection( - String sectionUri, { - String? spTCookie, - required Market country, - }) async { - final headers = { - 'app-platform': 'WebPlayer', - 'authorization': 'Bearer $accessToken', - 'content-type': 'application/json;charset=UTF-8', - 'dnt': '1', - 'origin': 'https://open.spotify.com', - 'referer': 'https://open.spotify.com/' - }; - final response = await _client.getUri( - Uri( - scheme: "https", - host: "api-partner.spotify.com", - path: "/pathfinder/v1/query", - queryParameters: { - "operationName": "homeSection", - "variables": jsonEncode({ - "timeZone": tz.local.name, - "sp_t": spTCookie ?? "", - "country": country.name, - "uri": sectionUri - }), - "extensions": jsonEncode( - { - "persistedQuery": { - "version": 1, - - /// GraphQL persisted Query hash - /// This can change overtime. We've to lookout for it - /// Docs: https://www.apollographql.com/docs/graphos/operations/persisted-queries/ - "sha256Hash": - "eb3fba2d388cf4fc4d696b1757a58584e9538a3b515ea742e9cc9465807340be", - } - }, - ), - }, - ), - options: Options(headers: headers), - ); - - final data = SpotifyHomeFeedSection.fromJson( - transformSectionItemJsonMap( - response.data["data"]["homeSections"]["sections"][0], - ), - ); - - return data; - } -} diff --git a/lib/services/metadata/endpoints/search.dart b/lib/services/metadata/endpoints/search.dart index 070628c2..be4e8e30 100644 --- a/lib/services/metadata/endpoints/search.dart +++ b/lib/services/metadata/endpoints/search.dart @@ -124,13 +124,13 @@ class MetadataPluginSearchEndpoint { ); } - Future> tracks( + Future> tracks( String query, { int? limit, int? offset, }) async { if (query.isEmpty) { - return SpotubePaginationResponseObject( + return SpotubePaginationResponseObject( items: [], total: 0, limit: limit ?? 20, @@ -148,9 +148,9 @@ class MetadataPluginSearchEndpoint { }..removeWhere((key, value) => value == null), ) as Map; - return SpotubePaginationResponseObject.fromJson( + return SpotubePaginationResponseObject.fromJson( raw.cast(), - (json) => SpotubeSimpleTrackObject.fromJson(json.cast()), + (json) => SpotubeFullTrackObject.fromJson(json.cast()), ); } } diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index ccff62b0..b862a83e 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -6,7 +6,7 @@ import 'package:flutter_cache_manager/flutter_cache_manager.dart'; import 'package:html/dom.dart' hide Text; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Element; -import 'package:spotify/spotify.dart'; +import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart'; import 'package:spotube/modules/root/update_dialog.dart'; @@ -279,46 +279,39 @@ abstract class ServiceUtils { return subtitle; } - static DateTime parseSpotifyAlbumDate(AlbumSimple? album) { - if (album == null || album.releaseDate == null) { + static DateTime parseSpotifyAlbumDate(SpotubeFullAlbumObject? album) { + if (album == null) { return DateTime.parse("1975-01-01"); } - switch (album.releaseDatePrecision ?? DatePrecision.year) { - case DatePrecision.day: - return DateTime.parse(album.releaseDate!); - case DatePrecision.month: - return DateTime.parse("${album.releaseDate}-01"); - case DatePrecision.year: - return DateTime.parse("${album.releaseDate}-01-01"); - } + return DateTime.parse(album.releaseDate); } - static List sortTracks(List tracks, SortBy sortBy) { + static List sortTracks( + List tracks, SortBy sortBy) { if (sortBy == SortBy.none) return tracks; return List.from(tracks) ..sort((a, b) { switch (sortBy) { case SortBy.ascending: - return a.name?.compareTo(b.name ?? "") ?? 0; + return a.name.compareTo(b.name); case SortBy.descending: - return b.name?.compareTo(a.name ?? "") ?? 0; - case SortBy.newest: - final aDate = parseSpotifyAlbumDate(a.album); - final bDate = parseSpotifyAlbumDate(b.album); - return bDate.compareTo(aDate); - case SortBy.oldest: - final aDate = parseSpotifyAlbumDate(a.album); - final bDate = parseSpotifyAlbumDate(b.album); - return aDate.compareTo(bDate); + return b.name.compareTo(a.name); + // TODO: We'll figure this one out later :') + // case SortBy.newest: + // final aDate = parseSpotifyAlbumDate(a.album); + // final bDate = parseSpotifyAlbumDate(b.album); + // return bDate.compareTo(aDate); + // case SortBy.oldest: + // final aDate = parseSpotifyAlbumDate(a.album); + // final bDate = parseSpotifyAlbumDate(b.album); + // return aDate.compareTo(bDate); case SortBy.duration: - return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0; + return a.durationMs.compareTo(b.durationMs); case SortBy.artist: - return a.artists?.first.name - ?.compareTo(b.artists?.first.name ?? "") ?? - 0; + return a.artists.first.name.compareTo(b.artists.first.name); case SortBy.album: - return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0; + return a.album.name.compareTo(b.album.name); default: return 0; } diff --git a/pubspec.lock b/pubspec.lock index 8c5211f7..909b8217 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2288,14 +2288,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.10.1" - spotify: - dependency: "direct main" - description: - name: spotify - sha256: "705f09a457a893973451c15f4072670ac4783d67e42c35c080c55a48dee3a01f" - url: "https://pub.dev" - source: hosted - version: "0.13.7" sprintf: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index d221c3d7..e8cc7ef5 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -156,7 +156,6 @@ dependencies: url: https://github.com/KRTirtho/hetu_spotube_plugin.git ref: main get_it: ^8.0.3 - spotify: ^0.13.7 dev_dependencies: build_runner: ^2.4.13 From d898d45b4a0bfa35416f846c6981c509115f9250 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 14:52:45 +0600 Subject: [PATCH 23/60] refactor: add database schema migrations --- drift_schemas/app_db/drift_schema_v7.json | 2 +- lib/models/database/database.dart | 8 + lib/models/database/database.steps.dart | 83 ++-- test/drift/app_db/generated/schema_v7.dart | 538 +++------------------ 4 files changed, 116 insertions(+), 515 deletions(-) diff --git a/drift_schemas/app_db/drift_schema_v7.json b/drift_schemas/app_db/drift_schema_v7.json index 4390b550..da3f8713 100644 --- a/drift_schemas/app_db/drift_schema_v7.json +++ b/drift_schemas/app_db/drift_schema_v7.json @@ -1 +1 @@ -{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[6],"type":"table","data":{"name":"playlist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_player_state_id","getter_name":"audioPlayerStateId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES audio_player_state_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES audio_player_state_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"index","getter_name":"index","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[7],"type":"table","data":{"name":"playlist_media_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playlist_id","getter_name":"playlistId","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"REFERENCES playlist_table (id)","dialectAwareDefaultConstraints":{"sqlite":"REFERENCES playlist_table (id)"},"default_dart":null,"default_client_dart":null,"dsl_features":["unknown"]},{"name":"uri","getter_name":"uri","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"extras","getter_name":"extras","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}},{"name":"http_headers","getter_name":"httpHeaders","moor_type":"string","nullable":true,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":11,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entry_point","getter_name":"entryPoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"apis","getter_name":"apis","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"abilities","getter_name":"abilities","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":12,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":13,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"tracks","getter_name":"tracks","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeTrackObjectListConverter()","dart_type_name":"List"}},{"name":"current_index","getter_name":"currentIndex","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entry_point","getter_name":"entryPoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"apis","getter_name":"apis","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"abilities","getter_name":"abilities","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":11,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file diff --git a/lib/models/database/database.dart b/lib/models/database/database.dart index 51b5b880..d12fec14 100644 --- a/lib/models/database/database.dart +++ b/lib/models/database/database.dart @@ -133,6 +133,14 @@ class AppDatabase extends _$AppDatabase { }, from6To7: (m, schema) async { await m.createTable(schema.metadataPluginsTable); + await m.addColumn( + schema.audioPlayerStateTable, + schema.audioPlayerStateTable.currentIndex, + ); + await m.addColumn( + schema.audioPlayerStateTable, + schema.audioPlayerStateTable.tracks, + ); }, ), ); diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index ef277bc5..1bfff438 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1704,8 +1704,6 @@ final class Schema7 extends i0.VersionedSchema { skipSegmentTable, sourceMatchTable, audioPlayerStateTable, - playlistTable, - playlistMediaTable, historyTable, lyricsTable, metadataPluginsTable, @@ -1829,7 +1827,7 @@ final class Schema7 extends i0.VersionedSchema { attachedDatabase: database, ), alias: null); - late final Shape6 audioPlayerStateTable = Shape6( + late final Shape14 audioPlayerStateTable = Shape14( source: i0.VersionedTable( entityName: 'audio_player_state_table', withoutRowId: false, @@ -1841,36 +1839,8 @@ final class Schema7 extends i0.VersionedSchema { _column_41, _column_42, _column_43, - ], - attachedDatabase: database, - ), - alias: null); - late final Shape7 playlistTable = Shape7( - source: i0.VersionedTable( - entityName: 'playlist_table', - withoutRowId: false, - isStrict: false, - tableConstraints: [], - columns: [ - _column_0, - _column_44, - _column_45, - ], - attachedDatabase: database, - ), - alias: null); - late final Shape8 playlistMediaTable = Shape8( - source: i0.VersionedTable( - entityName: 'playlist_media_table', - withoutRowId: false, - isStrict: false, - tableConstraints: [], - columns: [ - _column_0, - _column_46, - _column_47, - _column_48, - _column_49, + _column_57, + _column_58, ], attachedDatabase: database, ), @@ -1905,7 +1875,7 @@ final class Schema7 extends i0.VersionedSchema { attachedDatabase: database, ), alias: null); - late final Shape14 metadataPluginsTable = Shape14( + late final Shape15 metadataPluginsTable = Shape15( source: i0.VersionedTable( entityName: 'metadata_plugins_table', withoutRowId: false, @@ -1913,14 +1883,14 @@ final class Schema7 extends i0.VersionedSchema { tableConstraints: [], columns: [ _column_0, - _column_57, - _column_58, _column_59, _column_60, _column_61, _column_62, _column_63, _column_64, + _column_65, + _column_66, ], attachedDatabase: database, ), @@ -1933,6 +1903,31 @@ final class Schema7 extends i0.VersionedSchema { class Shape14 extends i0.VersionedTable { Shape14({required super.source, required super.alias}) : super.aliased(); + i1.GeneratedColumn get id => + columnsByName['id']! as i1.GeneratedColumn; + i1.GeneratedColumn get playing => + columnsByName['playing']! as i1.GeneratedColumn; + i1.GeneratedColumn get loopMode => + columnsByName['loop_mode']! as i1.GeneratedColumn; + i1.GeneratedColumn get shuffled => + columnsByName['shuffled']! as i1.GeneratedColumn; + i1.GeneratedColumn get collections => + columnsByName['collections']! as i1.GeneratedColumn; + i1.GeneratedColumn get tracks => + columnsByName['tracks']! as i1.GeneratedColumn; + i1.GeneratedColumn get currentIndex => + columnsByName['current_index']! as i1.GeneratedColumn; +} + +i1.GeneratedColumn _column_57(String aliasedName) => + i1.GeneratedColumn('tracks', aliasedName, false, + type: i1.DriftSqlType.string); +i1.GeneratedColumn _column_58(String aliasedName) => + i1.GeneratedColumn('current_index', aliasedName, false, + type: i1.DriftSqlType.int); + +class Shape15 extends i0.VersionedTable { + Shape15({required super.source, required super.alias}) : super.aliased(); i1.GeneratedColumn get id => columnsByName['id']! as i1.GeneratedColumn; i1.GeneratedColumn get name => @@ -1953,30 +1948,30 @@ class Shape14 extends i0.VersionedTable { columnsByName['selected']! as i1.GeneratedColumn; } -i1.GeneratedColumn _column_57(String aliasedName) => +i1.GeneratedColumn _column_59(String aliasedName) => i1.GeneratedColumn('name', aliasedName, false, additionalChecks: i1.GeneratedColumn.checkTextLength( minTextLength: 1, maxTextLength: 50), type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_58(String aliasedName) => +i1.GeneratedColumn _column_60(String aliasedName) => i1.GeneratedColumn('description', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_59(String aliasedName) => +i1.GeneratedColumn _column_61(String aliasedName) => i1.GeneratedColumn('version', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_60(String aliasedName) => +i1.GeneratedColumn _column_62(String aliasedName) => i1.GeneratedColumn('author', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_61(String aliasedName) => +i1.GeneratedColumn _column_63(String aliasedName) => i1.GeneratedColumn('entry_point', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_62(String aliasedName) => +i1.GeneratedColumn _column_64(String aliasedName) => i1.GeneratedColumn('apis', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_63(String aliasedName) => +i1.GeneratedColumn _column_65(String aliasedName) => i1.GeneratedColumn('abilities', aliasedName, false, type: i1.DriftSqlType.string); -i1.GeneratedColumn _column_64(String aliasedName) => +i1.GeneratedColumn _column_66(String aliasedName) => i1.GeneratedColumn('selected', aliasedName, false, type: i1.DriftSqlType.bool, defaultConstraints: i1.GeneratedColumn.constraintIsAlways( diff --git a/test/drift/app_db/generated/schema_v7.dart b/test/drift/app_db/generated/schema_v7.dart index 49f87c47..38a8e815 100644 --- a/test/drift/app_db/generated/schema_v7.dart +++ b/test/drift/app_db/generated/schema_v7.dart @@ -2299,9 +2299,15 @@ class AudioPlayerStateTable extends Table late final GeneratedColumn collections = GeneratedColumn( 'collections', aliasedName, false, type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn tracks = GeneratedColumn( + 'tracks', aliasedName, false, + type: DriftSqlType.string, requiredDuringInsert: true); + late final GeneratedColumn currentIndex = GeneratedColumn( + 'current_index', aliasedName, false, + type: DriftSqlType.int, requiredDuringInsert: true); @override List get $columns => - [id, playing, loopMode, shuffled, collections]; + [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; @override String get aliasedName => _alias ?? actualTableName; @override @@ -2324,6 +2330,10 @@ class AudioPlayerStateTable extends Table .read(DriftSqlType.bool, data['${effectivePrefix}shuffled'])!, collections: attachedDatabase.typeMapping .read(DriftSqlType.string, data['${effectivePrefix}collections'])!, + tracks: attachedDatabase.typeMapping + .read(DriftSqlType.string, data['${effectivePrefix}tracks'])!, + currentIndex: attachedDatabase.typeMapping + .read(DriftSqlType.int, data['${effectivePrefix}current_index'])!, ); } @@ -2340,12 +2350,16 @@ class AudioPlayerStateTableData extends DataClass final String loopMode; final bool shuffled; final String collections; + final String tracks; + final int currentIndex; const AudioPlayerStateTableData( {required this.id, required this.playing, required this.loopMode, required this.shuffled, - required this.collections}); + required this.collections, + required this.tracks, + required this.currentIndex}); @override Map toColumns(bool nullToAbsent) { final map = {}; @@ -2354,6 +2368,8 @@ class AudioPlayerStateTableData extends DataClass map['loop_mode'] = Variable(loopMode); map['shuffled'] = Variable(shuffled); map['collections'] = Variable(collections); + map['tracks'] = Variable(tracks); + map['current_index'] = Variable(currentIndex); return map; } @@ -2364,6 +2380,8 @@ class AudioPlayerStateTableData extends DataClass loopMode: Value(loopMode), shuffled: Value(shuffled), collections: Value(collections), + tracks: Value(tracks), + currentIndex: Value(currentIndex), ); } @@ -2376,6 +2394,8 @@ class AudioPlayerStateTableData extends DataClass loopMode: serializer.fromJson(json['loopMode']), shuffled: serializer.fromJson(json['shuffled']), collections: serializer.fromJson(json['collections']), + tracks: serializer.fromJson(json['tracks']), + currentIndex: serializer.fromJson(json['currentIndex']), ); } @override @@ -2387,6 +2407,8 @@ class AudioPlayerStateTableData extends DataClass 'loopMode': serializer.toJson(loopMode), 'shuffled': serializer.toJson(shuffled), 'collections': serializer.toJson(collections), + 'tracks': serializer.toJson(tracks), + 'currentIndex': serializer.toJson(currentIndex), }; } @@ -2395,13 +2417,17 @@ class AudioPlayerStateTableData extends DataClass bool? playing, String? loopMode, bool? shuffled, - String? collections}) => + String? collections, + String? tracks, + int? currentIndex}) => AudioPlayerStateTableData( id: id ?? this.id, playing: playing ?? this.playing, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, ); AudioPlayerStateTableData copyWithCompanion( AudioPlayerStateTableCompanion data) { @@ -2412,6 +2438,10 @@ class AudioPlayerStateTableData extends DataClass shuffled: data.shuffled.present ? data.shuffled.value : this.shuffled, collections: data.collections.present ? data.collections.value : this.collections, + tracks: data.tracks.present ? data.tracks.value : this.tracks, + currentIndex: data.currentIndex.present + ? data.currentIndex.value + : this.currentIndex, ); } @@ -2422,13 +2452,16 @@ class AudioPlayerStateTableData extends DataClass ..write('playing: $playing, ') ..write('loopMode: $loopMode, ') ..write('shuffled: $shuffled, ') - ..write('collections: $collections') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') ..write(')')) .toString(); } @override - int get hashCode => Object.hash(id, playing, loopMode, shuffled, collections); + int get hashCode => Object.hash( + id, playing, loopMode, shuffled, collections, tracks, currentIndex); @override bool operator ==(Object other) => identical(this, other) || @@ -2437,7 +2470,9 @@ class AudioPlayerStateTableData extends DataClass other.playing == this.playing && other.loopMode == this.loopMode && other.shuffled == this.shuffled && - other.collections == this.collections); + other.collections == this.collections && + other.tracks == this.tracks && + other.currentIndex == this.currentIndex); } class AudioPlayerStateTableCompanion @@ -2447,12 +2482,16 @@ class AudioPlayerStateTableCompanion final Value loopMode; final Value shuffled; final Value collections; + final Value tracks; + final Value currentIndex; const AudioPlayerStateTableCompanion({ this.id = const Value.absent(), this.playing = const Value.absent(), this.loopMode = const Value.absent(), this.shuffled = const Value.absent(), this.collections = const Value.absent(), + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), }); AudioPlayerStateTableCompanion.insert({ this.id = const Value.absent(), @@ -2460,16 +2499,22 @@ class AudioPlayerStateTableCompanion required String loopMode, required bool shuffled, required String collections, + required String tracks, + required int currentIndex, }) : playing = Value(playing), loopMode = Value(loopMode), shuffled = Value(shuffled), - collections = Value(collections); + collections = Value(collections), + tracks = Value(tracks), + currentIndex = Value(currentIndex); static Insertable custom({ Expression? id, Expression? playing, Expression? loopMode, Expression? shuffled, Expression? collections, + Expression? tracks, + Expression? currentIndex, }) { return RawValuesInsertable({ if (id != null) 'id': id, @@ -2477,6 +2522,8 @@ class AudioPlayerStateTableCompanion if (loopMode != null) 'loop_mode': loopMode, if (shuffled != null) 'shuffled': shuffled, if (collections != null) 'collections': collections, + if (tracks != null) 'tracks': tracks, + if (currentIndex != null) 'current_index': currentIndex, }); } @@ -2485,13 +2532,17 @@ class AudioPlayerStateTableCompanion Value? playing, Value? loopMode, Value? shuffled, - Value? collections}) { + Value? collections, + Value? tracks, + Value? currentIndex}) { return AudioPlayerStateTableCompanion( id: id ?? this.id, playing: playing ?? this.playing, loopMode: loopMode ?? this.loopMode, shuffled: shuffled ?? this.shuffled, collections: collections ?? this.collections, + tracks: tracks ?? this.tracks, + currentIndex: currentIndex ?? this.currentIndex, ); } @@ -2513,6 +2564,12 @@ class AudioPlayerStateTableCompanion if (collections.present) { map['collections'] = Variable(collections.value); } + if (tracks.present) { + map['tracks'] = Variable(tracks.value); + } + if (currentIndex.present) { + map['current_index'] = Variable(currentIndex.value); + } return map; } @@ -2523,464 +2580,9 @@ class AudioPlayerStateTableCompanion ..write('playing: $playing, ') ..write('loopMode: $loopMode, ') ..write('shuffled: $shuffled, ') - ..write('collections: $collections') - ..write(')')) - .toString(); - } -} - -class PlaylistTable extends Table - with TableInfo { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - PlaylistTable(this.attachedDatabase, [this._alias]); - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - late final GeneratedColumn audioPlayerStateId = GeneratedColumn( - 'audio_player_state_id', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: true, - defaultConstraints: GeneratedColumn.constraintIsAlways( - 'REFERENCES audio_player_state_table (id)')); - late final GeneratedColumn index = GeneratedColumn( - 'index', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); - @override - List get $columns => [id, audioPlayerStateId, index]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'playlist_table'; - @override - Set get $primaryKey => {id}; - @override - PlaylistTableData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlaylistTableData( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - audioPlayerStateId: attachedDatabase.typeMapping.read( - DriftSqlType.int, data['${effectivePrefix}audio_player_state_id'])!, - index: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}index'])!, - ); - } - - @override - PlaylistTable createAlias(String alias) { - return PlaylistTable(attachedDatabase, alias); - } -} - -class PlaylistTableData extends DataClass - implements Insertable { - final int id; - final int audioPlayerStateId; - final int index; - const PlaylistTableData( - {required this.id, - required this.audioPlayerStateId, - required this.index}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['audio_player_state_id'] = Variable(audioPlayerStateId); - map['index'] = Variable(index); - return map; - } - - PlaylistTableCompanion toCompanion(bool nullToAbsent) { - return PlaylistTableCompanion( - id: Value(id), - audioPlayerStateId: Value(audioPlayerStateId), - index: Value(index), - ); - } - - factory PlaylistTableData.fromJson(Map json, - {ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return PlaylistTableData( - id: serializer.fromJson(json['id']), - audioPlayerStateId: serializer.fromJson(json['audioPlayerStateId']), - index: serializer.fromJson(json['index']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'audioPlayerStateId': serializer.toJson(audioPlayerStateId), - 'index': serializer.toJson(index), - }; - } - - PlaylistTableData copyWith({int? id, int? audioPlayerStateId, int? index}) => - PlaylistTableData( - id: id ?? this.id, - audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, - index: index ?? this.index, - ); - PlaylistTableData copyWithCompanion(PlaylistTableCompanion data) { - return PlaylistTableData( - id: data.id.present ? data.id.value : this.id, - audioPlayerStateId: data.audioPlayerStateId.present - ? data.audioPlayerStateId.value - : this.audioPlayerStateId, - index: data.index.present ? data.index.value : this.index, - ); - } - - @override - String toString() { - return (StringBuffer('PlaylistTableData(') - ..write('id: $id, ') - ..write('audioPlayerStateId: $audioPlayerStateId, ') - ..write('index: $index') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, audioPlayerStateId, index); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is PlaylistTableData && - other.id == this.id && - other.audioPlayerStateId == this.audioPlayerStateId && - other.index == this.index); -} - -class PlaylistTableCompanion extends UpdateCompanion { - final Value id; - final Value audioPlayerStateId; - final Value index; - const PlaylistTableCompanion({ - this.id = const Value.absent(), - this.audioPlayerStateId = const Value.absent(), - this.index = const Value.absent(), - }); - PlaylistTableCompanion.insert({ - this.id = const Value.absent(), - required int audioPlayerStateId, - required int index, - }) : audioPlayerStateId = Value(audioPlayerStateId), - index = Value(index); - static Insertable custom({ - Expression? id, - Expression? audioPlayerStateId, - Expression? index, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (audioPlayerStateId != null) - 'audio_player_state_id': audioPlayerStateId, - if (index != null) 'index': index, - }); - } - - PlaylistTableCompanion copyWith( - {Value? id, Value? audioPlayerStateId, Value? index}) { - return PlaylistTableCompanion( - id: id ?? this.id, - audioPlayerStateId: audioPlayerStateId ?? this.audioPlayerStateId, - index: index ?? this.index, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (audioPlayerStateId.present) { - map['audio_player_state_id'] = Variable(audioPlayerStateId.value); - } - if (index.present) { - map['index'] = Variable(index.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('PlaylistTableCompanion(') - ..write('id: $id, ') - ..write('audioPlayerStateId: $audioPlayerStateId, ') - ..write('index: $index') - ..write(')')) - .toString(); - } -} - -class PlaylistMediaTable extends Table - with TableInfo { - @override - final GeneratedDatabase attachedDatabase; - final String? _alias; - PlaylistMediaTable(this.attachedDatabase, [this._alias]); - late final GeneratedColumn id = GeneratedColumn( - 'id', aliasedName, false, - hasAutoIncrement: true, - type: DriftSqlType.int, - requiredDuringInsert: false, - defaultConstraints: - GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); - late final GeneratedColumn playlistId = GeneratedColumn( - 'playlist_id', aliasedName, false, - type: DriftSqlType.int, - requiredDuringInsert: true, - defaultConstraints: - GeneratedColumn.constraintIsAlways('REFERENCES playlist_table (id)')); - late final GeneratedColumn uri = GeneratedColumn( - 'uri', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); - late final GeneratedColumn extras = GeneratedColumn( - 'extras', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - late final GeneratedColumn httpHeaders = GeneratedColumn( - 'http_headers', aliasedName, true, - type: DriftSqlType.string, requiredDuringInsert: false); - @override - List get $columns => - [id, playlistId, uri, extras, httpHeaders]; - @override - String get aliasedName => _alias ?? actualTableName; - @override - String get actualTableName => $name; - static const String $name = 'playlist_media_table'; - @override - Set get $primaryKey => {id}; - @override - PlaylistMediaTableData map(Map data, {String? tablePrefix}) { - final effectivePrefix = tablePrefix != null ? '$tablePrefix.' : ''; - return PlaylistMediaTableData( - id: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}id'])!, - playlistId: attachedDatabase.typeMapping - .read(DriftSqlType.int, data['${effectivePrefix}playlist_id'])!, - uri: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}uri'])!, - extras: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}extras']), - httpHeaders: attachedDatabase.typeMapping - .read(DriftSqlType.string, data['${effectivePrefix}http_headers']), - ); - } - - @override - PlaylistMediaTable createAlias(String alias) { - return PlaylistMediaTable(attachedDatabase, alias); - } -} - -class PlaylistMediaTableData extends DataClass - implements Insertable { - final int id; - final int playlistId; - final String uri; - final String? extras; - final String? httpHeaders; - const PlaylistMediaTableData( - {required this.id, - required this.playlistId, - required this.uri, - this.extras, - this.httpHeaders}); - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - map['id'] = Variable(id); - map['playlist_id'] = Variable(playlistId); - map['uri'] = Variable(uri); - if (!nullToAbsent || extras != null) { - map['extras'] = Variable(extras); - } - if (!nullToAbsent || httpHeaders != null) { - map['http_headers'] = Variable(httpHeaders); - } - return map; - } - - PlaylistMediaTableCompanion toCompanion(bool nullToAbsent) { - return PlaylistMediaTableCompanion( - id: Value(id), - playlistId: Value(playlistId), - uri: Value(uri), - extras: - extras == null && nullToAbsent ? const Value.absent() : Value(extras), - httpHeaders: httpHeaders == null && nullToAbsent - ? const Value.absent() - : Value(httpHeaders), - ); - } - - factory PlaylistMediaTableData.fromJson(Map json, - {ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return PlaylistMediaTableData( - id: serializer.fromJson(json['id']), - playlistId: serializer.fromJson(json['playlistId']), - uri: serializer.fromJson(json['uri']), - extras: serializer.fromJson(json['extras']), - httpHeaders: serializer.fromJson(json['httpHeaders']), - ); - } - @override - Map toJson({ValueSerializer? serializer}) { - serializer ??= driftRuntimeOptions.defaultSerializer; - return { - 'id': serializer.toJson(id), - 'playlistId': serializer.toJson(playlistId), - 'uri': serializer.toJson(uri), - 'extras': serializer.toJson(extras), - 'httpHeaders': serializer.toJson(httpHeaders), - }; - } - - PlaylistMediaTableData copyWith( - {int? id, - int? playlistId, - String? uri, - Value extras = const Value.absent(), - Value httpHeaders = const Value.absent()}) => - PlaylistMediaTableData( - id: id ?? this.id, - playlistId: playlistId ?? this.playlistId, - uri: uri ?? this.uri, - extras: extras.present ? extras.value : this.extras, - httpHeaders: httpHeaders.present ? httpHeaders.value : this.httpHeaders, - ); - PlaylistMediaTableData copyWithCompanion(PlaylistMediaTableCompanion data) { - return PlaylistMediaTableData( - id: data.id.present ? data.id.value : this.id, - playlistId: - data.playlistId.present ? data.playlistId.value : this.playlistId, - uri: data.uri.present ? data.uri.value : this.uri, - extras: data.extras.present ? data.extras.value : this.extras, - httpHeaders: - data.httpHeaders.present ? data.httpHeaders.value : this.httpHeaders, - ); - } - - @override - String toString() { - return (StringBuffer('PlaylistMediaTableData(') - ..write('id: $id, ') - ..write('playlistId: $playlistId, ') - ..write('uri: $uri, ') - ..write('extras: $extras, ') - ..write('httpHeaders: $httpHeaders') - ..write(')')) - .toString(); - } - - @override - int get hashCode => Object.hash(id, playlistId, uri, extras, httpHeaders); - @override - bool operator ==(Object other) => - identical(this, other) || - (other is PlaylistMediaTableData && - other.id == this.id && - other.playlistId == this.playlistId && - other.uri == this.uri && - other.extras == this.extras && - other.httpHeaders == this.httpHeaders); -} - -class PlaylistMediaTableCompanion - extends UpdateCompanion { - final Value id; - final Value playlistId; - final Value uri; - final Value extras; - final Value httpHeaders; - const PlaylistMediaTableCompanion({ - this.id = const Value.absent(), - this.playlistId = const Value.absent(), - this.uri = const Value.absent(), - this.extras = const Value.absent(), - this.httpHeaders = const Value.absent(), - }); - PlaylistMediaTableCompanion.insert({ - this.id = const Value.absent(), - required int playlistId, - required String uri, - this.extras = const Value.absent(), - this.httpHeaders = const Value.absent(), - }) : playlistId = Value(playlistId), - uri = Value(uri); - static Insertable custom({ - Expression? id, - Expression? playlistId, - Expression? uri, - Expression? extras, - Expression? httpHeaders, - }) { - return RawValuesInsertable({ - if (id != null) 'id': id, - if (playlistId != null) 'playlist_id': playlistId, - if (uri != null) 'uri': uri, - if (extras != null) 'extras': extras, - if (httpHeaders != null) 'http_headers': httpHeaders, - }); - } - - PlaylistMediaTableCompanion copyWith( - {Value? id, - Value? playlistId, - Value? uri, - Value? extras, - Value? httpHeaders}) { - return PlaylistMediaTableCompanion( - id: id ?? this.id, - playlistId: playlistId ?? this.playlistId, - uri: uri ?? this.uri, - extras: extras ?? this.extras, - httpHeaders: httpHeaders ?? this.httpHeaders, - ); - } - - @override - Map toColumns(bool nullToAbsent) { - final map = {}; - if (id.present) { - map['id'] = Variable(id.value); - } - if (playlistId.present) { - map['playlist_id'] = Variable(playlistId.value); - } - if (uri.present) { - map['uri'] = Variable(uri.value); - } - if (extras.present) { - map['extras'] = Variable(extras.value); - } - if (httpHeaders.present) { - map['http_headers'] = Variable(httpHeaders.value); - } - return map; - } - - @override - String toString() { - return (StringBuffer('PlaylistMediaTableCompanion(') - ..write('id: $id, ') - ..write('playlistId: $playlistId, ') - ..write('uri: $uri, ') - ..write('extras: $extras, ') - ..write('httpHeaders: $httpHeaders') + ..write('collections: $collections, ') + ..write('tracks: $tracks, ') + ..write('currentIndex: $currentIndex') ..write(')')) .toString(); } @@ -3817,8 +3419,6 @@ class DatabaseAtV7 extends GeneratedDatabase { late final SourceMatchTable sourceMatchTable = SourceMatchTable(this); late final AudioPlayerStateTable audioPlayerStateTable = AudioPlayerStateTable(this); - late final PlaylistTable playlistTable = PlaylistTable(this); - late final PlaylistMediaTable playlistMediaTable = PlaylistMediaTable(this); late final HistoryTable historyTable = HistoryTable(this); late final LyricsTable lyricsTable = LyricsTable(this); late final MetadataPluginsTable metadataPluginsTable = @@ -3839,8 +3439,6 @@ class DatabaseAtV7 extends GeneratedDatabase { skipSegmentTable, sourceMatchTable, audioPlayerStateTable, - playlistTable, - playlistMediaTable, historyTable, lyricsTable, metadataPluginsTable, From 1eb0b5f8475a6ee9e9a07f03ff032ecb2e3c5b52 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 15:01:58 +0600 Subject: [PATCH 24/60] chore: uncomment important lines of code --- lib/collections/routes.dart | 16 - lib/collections/routes.gr.dart | 638 +++++++------------ lib/components/track_tile/track_options.dart | 6 +- lib/modules/stats/common/playlist_item.dart | 4 +- lib/pages/album/album.dart | 5 +- lib/pages/track/track.dart | 22 +- lib/provider/lyrics/synced.dart | 47 -- 7 files changed, 266 insertions(+), 472 deletions(-) diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 2c1ad8ad..526da1c9 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -39,14 +39,6 @@ class AppRouter extends RootStackRouter { ), ], ), - AutoRoute( - path: "home/genres", - page: GenreRoute.page, - ), - AutoRoute( - path: "home/genre/:categoryId", - page: GenrePlaylistsRoute.page, - ), AutoRoute( path: "home/sections/:sectionId", page: HomeBrowseSectionItemsRoute.page, @@ -86,14 +78,6 @@ class AppRouter extends RootStackRouter { page: LocalLibraryRoute.page, // parentNavigatorKey: shellRouteNavigatorKey, ), - AutoRoute( - path: "library/generate", - page: PlaylistGeneratorRoute.page, - ), - AutoRoute( - path: "library/generate/result", - page: PlaylistGenerateResultRoute.page, - ), AutoRoute( path: "lyrics", page: LyricsRoute.page, diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index c97e4ce9..a3746de1 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,64 +8,56 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i44; -import 'package:flutter/material.dart' as _i45; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i48; -import 'package:spotify/spotify.dart' as _i47; -import 'package:spotube/models/metadata/metadata.dart' as _i46; -import 'package:spotube/models/spotify/recommendation_seeds.dart' as _i49; +import 'package:auto_route/auto_route.dart' as _i40; +import 'package:flutter/material.dart' as _i41; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i43; +import 'package:spotube/models/metadata/metadata.dart' as _i42; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; import 'package:spotube/pages/connect/control/control.dart' as _i5; -import 'package:spotube/pages/getting_started/getting_started.dart' as _i9; -import 'package:spotube/pages/home/genres/genre_playlists.dart' as _i8; -import 'package:spotube/pages/home/genres/genres.dart' as _i7; -import 'package:spotube/pages/home/home.dart' as _i11; -import 'package:spotube/pages/home/sections/section_items.dart' as _i10; -import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i12; -import 'package:spotube/pages/library/library.dart' as _i13; -import 'package:spotube/pages/library/playlist_generate/playlist_generate.dart' - as _i23; -import 'package:spotube/pages/library/playlist_generate/playlist_generate_result.dart' - as _i22; -import 'package:spotube/pages/library/user_albums.dart' as _i38; -import 'package:spotube/pages/library/user_artists.dart' as _i39; -import 'package:spotube/pages/library/user_downloads.dart' as _i40; +import 'package:spotube/pages/getting_started/getting_started.dart' as _i7; +import 'package:spotube/pages/home/home.dart' as _i9; +import 'package:spotube/pages/home/sections/section_items.dart' as _i8; +import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i10; +import 'package:spotube/pages/library/library.dart' as _i11; +import 'package:spotube/pages/library/user_albums.dart' as _i34; +import 'package:spotube/pages/library/user_artists.dart' as _i35; +import 'package:spotube/pages/library/user_downloads.dart' as _i36; import 'package:spotube/pages/library/user_local_tracks/local_folder.dart' - as _i15; + as _i13; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' - as _i41; -import 'package:spotube/pages/library/user_playlists.dart' as _i42; -import 'package:spotube/pages/lyrics/lyrics.dart' as _i17; -import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i18; -import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i43; -import 'package:spotube/pages/player/lyrics.dart' as _i19; -import 'package:spotube/pages/player/queue.dart' as _i20; -import 'package:spotube/pages/player/sources.dart' as _i21; -import 'package:spotube/pages/playlist/liked_playlist.dart' as _i14; -import 'package:spotube/pages/playlist/playlist.dart' as _i24; -import 'package:spotube/pages/profile/profile.dart' as _i25; -import 'package:spotube/pages/root/root_app.dart' as _i26; -import 'package:spotube/pages/search/search.dart' as _i27; + as _i37; +import 'package:spotube/pages/library/user_playlists.dart' as _i38; +import 'package:spotube/pages/lyrics/lyrics.dart' as _i15; +import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i16; +import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i39; +import 'package:spotube/pages/player/lyrics.dart' as _i17; +import 'package:spotube/pages/player/queue.dart' as _i18; +import 'package:spotube/pages/player/sources.dart' as _i19; +import 'package:spotube/pages/playlist/liked_playlist.dart' as _i12; +import 'package:spotube/pages/playlist/playlist.dart' as _i20; +import 'package:spotube/pages/profile/profile.dart' as _i21; +import 'package:spotube/pages/root/root_app.dart' as _i22; +import 'package:spotube/pages/search/search.dart' as _i23; import 'package:spotube/pages/settings/about.dart' as _i1; import 'package:spotube/pages/settings/blacklist.dart' as _i4; -import 'package:spotube/pages/settings/logs.dart' as _i16; -import 'package:spotube/pages/settings/metadata_plugins.dart' as _i28; -import 'package:spotube/pages/settings/settings.dart' as _i29; -import 'package:spotube/pages/stats/albums/albums.dart' as _i30; -import 'package:spotube/pages/stats/artists/artists.dart' as _i31; -import 'package:spotube/pages/stats/fees/fees.dart' as _i35; -import 'package:spotube/pages/stats/minutes/minutes.dart' as _i32; -import 'package:spotube/pages/stats/playlists/playlists.dart' as _i34; -import 'package:spotube/pages/stats/stats.dart' as _i33; -import 'package:spotube/pages/stats/streams/streams.dart' as _i36; -import 'package:spotube/pages/track/track.dart' as _i37; +import 'package:spotube/pages/settings/logs.dart' as _i14; +import 'package:spotube/pages/settings/metadata_plugins.dart' as _i24; +import 'package:spotube/pages/settings/settings.dart' as _i25; +import 'package:spotube/pages/stats/albums/albums.dart' as _i26; +import 'package:spotube/pages/stats/artists/artists.dart' as _i27; +import 'package:spotube/pages/stats/fees/fees.dart' as _i31; +import 'package:spotube/pages/stats/minutes/minutes.dart' as _i28; +import 'package:spotube/pages/stats/playlists/playlists.dart' as _i30; +import 'package:spotube/pages/stats/stats.dart' as _i29; +import 'package:spotube/pages/stats/streams/streams.dart' as _i32; +import 'package:spotube/pages/track/track.dart' as _i33; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i44.PageRouteInfo { - const AboutSpotubeRoute({List<_i44.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i40.PageRouteInfo { + const AboutSpotubeRoute({List<_i40.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -73,7 +65,7 @@ class AboutSpotubeRoute extends _i44.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -83,12 +75,12 @@ class AboutSpotubeRoute extends _i44.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i44.PageRouteInfo { +class AlbumRoute extends _i40.PageRouteInfo { AlbumRoute({ - _i45.Key? key, + _i41.Key? key, required String id, - required _i46.SpotubeSimpleAlbumObject album, - List<_i44.PageRouteInfo>? children, + required _i42.SpotubeSimpleAlbumObject album, + List<_i40.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -102,7 +94,7 @@ class AlbumRoute extends _i44.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -122,11 +114,11 @@ class AlbumRouteArgs { required this.album, }); - final _i45.Key? key; + final _i41.Key? key; final String id; - final _i46.SpotubeSimpleAlbumObject album; + final _i42.SpotubeSimpleAlbumObject album; @override String toString() { @@ -136,11 +128,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i44.PageRouteInfo { +class ArtistRoute extends _i40.PageRouteInfo { ArtistRoute({ required String artistId, - _i45.Key? key, - List<_i44.PageRouteInfo>? children, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -153,7 +145,7 @@ class ArtistRoute extends _i44.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -175,7 +167,7 @@ class ArtistRouteArgs { final String artistId; - final _i45.Key? key; + final _i41.Key? key; @override String toString() { @@ -185,8 +177,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i44.PageRouteInfo { - const BlackListRoute({List<_i44.PageRouteInfo>? children}) +class BlackListRoute extends _i40.PageRouteInfo { + const BlackListRoute({List<_i40.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -194,7 +186,7 @@ class BlackListRoute extends _i44.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -204,8 +196,8 @@ class BlackListRoute extends _i44.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i44.PageRouteInfo { - const ConnectControlRoute({List<_i44.PageRouteInfo>? children}) +class ConnectControlRoute extends _i40.PageRouteInfo { + const ConnectControlRoute({List<_i40.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -213,7 +205,7 @@ class ConnectControlRoute extends _i44.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -223,8 +215,8 @@ class ConnectControlRoute extends _i44.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i44.PageRouteInfo { - const ConnectRoute({List<_i44.PageRouteInfo>? children}) +class ConnectRoute extends _i40.PageRouteInfo { + const ConnectRoute({List<_i40.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -232,7 +224,7 @@ class ConnectRoute extends _i44.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -241,81 +233,9 @@ class ConnectRoute extends _i44.PageRouteInfo { } /// generated route for -/// [_i7.GenrePage] -class GenreRoute extends _i44.PageRouteInfo { - const GenreRoute({List<_i44.PageRouteInfo>? children}) - : super( - GenreRoute.name, - initialChildren: children, - ); - - static const String name = 'GenreRoute'; - - static _i44.PageInfo page = _i44.PageInfo( - name, - builder: (data) { - return const _i7.GenrePage(); - }, - ); -} - -/// generated route for -/// [_i8.GenrePlaylistsPage] -class GenrePlaylistsRoute extends _i44.PageRouteInfo { - GenrePlaylistsRoute({ - _i45.Key? key, - required String id, - required _i47.Category category, - List<_i44.PageRouteInfo>? children, - }) : super( - GenrePlaylistsRoute.name, - args: GenrePlaylistsRouteArgs( - key: key, - id: id, - category: category, - ), - rawPathParams: {'categoryId': id}, - initialChildren: children, - ); - - static const String name = 'GenrePlaylistsRoute'; - - static _i44.PageInfo page = _i44.PageInfo( - name, - builder: (data) { - final args = data.argsAs(); - return _i8.GenrePlaylistsPage( - key: args.key, - id: args.id, - category: args.category, - ); - }, - ); -} - -class GenrePlaylistsRouteArgs { - const GenrePlaylistsRouteArgs({ - this.key, - required this.id, - required this.category, - }); - - final _i45.Key? key; - - final String id; - - final _i47.Category category; - - @override - String toString() { - return 'GenrePlaylistsRouteArgs{key: $key, id: $id, category: $category}'; - } -} - -/// generated route for -/// [_i9.GettingStartedPage] -class GettingStartedRoute extends _i44.PageRouteInfo { - const GettingStartedRoute({List<_i44.PageRouteInfo>? children}) +/// [_i7.GettingStartedPage] +class GettingStartedRoute extends _i40.PageRouteInfo { + const GettingStartedRoute({List<_i40.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -323,23 +243,23 @@ class GettingStartedRoute extends _i44.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i9.GettingStartedPage(); + return const _i7.GettingStartedPage(); }, ); } /// generated route for -/// [_i10.HomeBrowseSectionItemsPage] +/// [_i8.HomeBrowseSectionItemsPage] class HomeBrowseSectionItemsRoute - extends _i44.PageRouteInfo { + extends _i40.PageRouteInfo { HomeBrowseSectionItemsRoute({ - _i48.Key? key, + _i43.Key? key, required String sectionId, - required _i46.SpotubeBrowseSectionObject section, - List<_i44.PageRouteInfo>? children, + required _i42.SpotubeBrowseSectionObject section, + List<_i40.PageRouteInfo>? children, }) : super( HomeBrowseSectionItemsRoute.name, args: HomeBrowseSectionItemsRouteArgs( @@ -353,11 +273,11 @@ class HomeBrowseSectionItemsRoute static const String name = 'HomeBrowseSectionItemsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i10.HomeBrowseSectionItemsPage( + return _i8.HomeBrowseSectionItemsPage( key: args.key, sectionId: args.sectionId, section: args.section, @@ -373,11 +293,11 @@ class HomeBrowseSectionItemsRouteArgs { required this.section, }); - final _i48.Key? key; + final _i43.Key? key; final String sectionId; - final _i46.SpotubeBrowseSectionObject section; + final _i42.SpotubeBrowseSectionObject section; @override String toString() { @@ -386,9 +306,9 @@ class HomeBrowseSectionItemsRouteArgs { } /// generated route for -/// [_i11.HomePage] -class HomeRoute extends _i44.PageRouteInfo { - const HomeRoute({List<_i44.PageRouteInfo>? children}) +/// [_i9.HomePage] +class HomeRoute extends _i40.PageRouteInfo { + const HomeRoute({List<_i40.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -396,18 +316,18 @@ class HomeRoute extends _i44.PageRouteInfo { static const String name = 'HomeRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i11.HomePage(); + return const _i9.HomePage(); }, ); } /// generated route for -/// [_i12.LastFMLoginPage] -class LastFMLoginRoute extends _i44.PageRouteInfo { - const LastFMLoginRoute({List<_i44.PageRouteInfo>? children}) +/// [_i10.LastFMLoginPage] +class LastFMLoginRoute extends _i40.PageRouteInfo { + const LastFMLoginRoute({List<_i40.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -415,18 +335,18 @@ class LastFMLoginRoute extends _i44.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i12.LastFMLoginPage(); + return const _i10.LastFMLoginPage(); }, ); } /// generated route for -/// [_i13.LibraryPage] -class LibraryRoute extends _i44.PageRouteInfo { - const LibraryRoute({List<_i44.PageRouteInfo>? children}) +/// [_i11.LibraryPage] +class LibraryRoute extends _i40.PageRouteInfo { + const LibraryRoute({List<_i40.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -434,21 +354,21 @@ class LibraryRoute extends _i44.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i13.LibraryPage(); + return const _i11.LibraryPage(); }, ); } /// generated route for -/// [_i14.LikedPlaylistPage] -class LikedPlaylistRoute extends _i44.PageRouteInfo { +/// [_i12.LikedPlaylistPage] +class LikedPlaylistRoute extends _i40.PageRouteInfo { LikedPlaylistRoute({ - _i45.Key? key, - required _i46.SpotubeSimplePlaylistObject playlist, - List<_i44.PageRouteInfo>? children, + _i41.Key? key, + required _i42.SpotubeSimplePlaylistObject playlist, + List<_i40.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -460,11 +380,11 @@ class LikedPlaylistRoute extends _i44.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i14.LikedPlaylistPage( + return _i12.LikedPlaylistPage( key: args.key, playlist: args.playlist, ); @@ -478,9 +398,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i45.Key? key; + final _i41.Key? key; - final _i46.SpotubeSimplePlaylistObject playlist; + final _i42.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -489,14 +409,14 @@ class LikedPlaylistRouteArgs { } /// generated route for -/// [_i15.LocalLibraryPage] -class LocalLibraryRoute extends _i44.PageRouteInfo { +/// [_i13.LocalLibraryPage] +class LocalLibraryRoute extends _i40.PageRouteInfo { LocalLibraryRoute({ required String location, - _i45.Key? key, + _i41.Key? key, bool isDownloads = false, bool isCache = false, - List<_i44.PageRouteInfo>? children, + List<_i40.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -510,11 +430,11 @@ class LocalLibraryRoute extends _i44.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i15.LocalLibraryPage( + return _i13.LocalLibraryPage( args.location, key: args.key, isDownloads: args.isDownloads, @@ -534,7 +454,7 @@ class LocalLibraryRouteArgs { final String location; - final _i45.Key? key; + final _i41.Key? key; final bool isDownloads; @@ -547,9 +467,9 @@ class LocalLibraryRouteArgs { } /// generated route for -/// [_i16.LogsPage] -class LogsRoute extends _i44.PageRouteInfo { - const LogsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i14.LogsPage] +class LogsRoute extends _i40.PageRouteInfo { + const LogsRoute({List<_i40.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -557,18 +477,18 @@ class LogsRoute extends _i44.PageRouteInfo { static const String name = 'LogsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i16.LogsPage(); + return const _i14.LogsPage(); }, ); } /// generated route for -/// [_i17.LyricsPage] -class LyricsRoute extends _i44.PageRouteInfo { - const LyricsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i15.LyricsPage] +class LyricsRoute extends _i40.PageRouteInfo { + const LyricsRoute({List<_i40.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -576,21 +496,21 @@ class LyricsRoute extends _i44.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i17.LyricsPage(); + return const _i15.LyricsPage(); }, ); } /// generated route for -/// [_i18.MiniLyricsPage] -class MiniLyricsRoute extends _i44.PageRouteInfo { +/// [_i16.MiniLyricsPage] +class MiniLyricsRoute extends _i40.PageRouteInfo { MiniLyricsRoute({ - _i48.Key? key, - required _i48.Size prevSize, - List<_i44.PageRouteInfo>? children, + _i43.Key? key, + required _i43.Size prevSize, + List<_i40.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -602,11 +522,11 @@ class MiniLyricsRoute extends _i44.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i18.MiniLyricsPage( + return _i16.MiniLyricsPage( key: args.key, prevSize: args.prevSize, ); @@ -620,9 +540,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i48.Key? key; + final _i43.Key? key; - final _i48.Size prevSize; + final _i43.Size prevSize; @override String toString() { @@ -631,9 +551,9 @@ class MiniLyricsRouteArgs { } /// generated route for -/// [_i19.PlayerLyricsPage] -class PlayerLyricsRoute extends _i44.PageRouteInfo { - const PlayerLyricsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i17.PlayerLyricsPage] +class PlayerLyricsRoute extends _i40.PageRouteInfo { + const PlayerLyricsRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -641,18 +561,18 @@ class PlayerLyricsRoute extends _i44.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i19.PlayerLyricsPage(); + return const _i17.PlayerLyricsPage(); }, ); } /// generated route for -/// [_i20.PlayerQueuePage] -class PlayerQueueRoute extends _i44.PageRouteInfo { - const PlayerQueueRoute({List<_i44.PageRouteInfo>? children}) +/// [_i18.PlayerQueuePage] +class PlayerQueueRoute extends _i40.PageRouteInfo { + const PlayerQueueRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -660,18 +580,18 @@ class PlayerQueueRoute extends _i44.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i20.PlayerQueuePage(); + return const _i18.PlayerQueuePage(); }, ); } /// generated route for -/// [_i21.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i44.PageRouteInfo>? children}) +/// [_i19.PlayerTrackSourcesPage] +class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -679,88 +599,22 @@ class PlayerTrackSourcesRoute extends _i44.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i21.PlayerTrackSourcesPage(); + return const _i19.PlayerTrackSourcesPage(); }, ); } /// generated route for -/// [_i22.PlaylistGenerateResultPage] -class PlaylistGenerateResultRoute - extends _i44.PageRouteInfo { - PlaylistGenerateResultRoute({ - _i48.Key? key, - required _i49.GeneratePlaylistProviderInput state, - List<_i44.PageRouteInfo>? children, - }) : super( - PlaylistGenerateResultRoute.name, - args: PlaylistGenerateResultRouteArgs( - key: key, - state: state, - ), - initialChildren: children, - ); - - static const String name = 'PlaylistGenerateResultRoute'; - - static _i44.PageInfo page = _i44.PageInfo( - name, - builder: (data) { - final args = data.argsAs(); - return _i22.PlaylistGenerateResultPage( - key: args.key, - state: args.state, - ); - }, - ); -} - -class PlaylistGenerateResultRouteArgs { - const PlaylistGenerateResultRouteArgs({ - this.key, - required this.state, - }); - - final _i48.Key? key; - - final _i49.GeneratePlaylistProviderInput state; - - @override - String toString() { - return 'PlaylistGenerateResultRouteArgs{key: $key, state: $state}'; - } -} - -/// generated route for -/// [_i23.PlaylistGeneratorPage] -class PlaylistGeneratorRoute extends _i44.PageRouteInfo { - const PlaylistGeneratorRoute({List<_i44.PageRouteInfo>? children}) - : super( - PlaylistGeneratorRoute.name, - initialChildren: children, - ); - - static const String name = 'PlaylistGeneratorRoute'; - - static _i44.PageInfo page = _i44.PageInfo( - name, - builder: (data) { - return const _i23.PlaylistGeneratorPage(); - }, - ); -} - -/// generated route for -/// [_i24.PlaylistPage] -class PlaylistRoute extends _i44.PageRouteInfo { +/// [_i20.PlaylistPage] +class PlaylistRoute extends _i40.PageRouteInfo { PlaylistRoute({ - _i45.Key? key, + _i41.Key? key, required String id, - required _i46.SpotubeSimplePlaylistObject playlist, - List<_i44.PageRouteInfo>? children, + required _i42.SpotubeSimplePlaylistObject playlist, + List<_i40.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -774,11 +628,11 @@ class PlaylistRoute extends _i44.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); - return _i24.PlaylistPage( + return _i20.PlaylistPage( key: args.key, id: args.id, playlist: args.playlist, @@ -794,11 +648,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i45.Key? key; + final _i41.Key? key; final String id; - final _i46.SpotubeSimplePlaylistObject playlist; + final _i42.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -807,9 +661,9 @@ class PlaylistRouteArgs { } /// generated route for -/// [_i25.ProfilePage] -class ProfileRoute extends _i44.PageRouteInfo { - const ProfileRoute({List<_i44.PageRouteInfo>? children}) +/// [_i21.ProfilePage] +class ProfileRoute extends _i40.PageRouteInfo { + const ProfileRoute({List<_i40.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -817,18 +671,18 @@ class ProfileRoute extends _i44.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i25.ProfilePage(); + return const _i21.ProfilePage(); }, ); } /// generated route for -/// [_i26.RootAppPage] -class RootAppRoute extends _i44.PageRouteInfo { - const RootAppRoute({List<_i44.PageRouteInfo>? children}) +/// [_i22.RootAppPage] +class RootAppRoute extends _i40.PageRouteInfo { + const RootAppRoute({List<_i40.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -836,18 +690,18 @@ class RootAppRoute extends _i44.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i26.RootAppPage(); + return const _i22.RootAppPage(); }, ); } /// generated route for -/// [_i27.SearchPage] -class SearchRoute extends _i44.PageRouteInfo { - const SearchRoute({List<_i44.PageRouteInfo>? children}) +/// [_i23.SearchPage] +class SearchRoute extends _i40.PageRouteInfo { + const SearchRoute({List<_i40.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -855,18 +709,18 @@ class SearchRoute extends _i44.PageRouteInfo { static const String name = 'SearchRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i27.SearchPage(); + return const _i23.SearchPage(); }, ); } /// generated route for -/// [_i28.SettingsMetadataProviderPage] -class SettingsMetadataProviderRoute extends _i44.PageRouteInfo { - const SettingsMetadataProviderRoute({List<_i44.PageRouteInfo>? children}) +/// [_i24.SettingsMetadataProviderPage] +class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i40.PageRouteInfo>? children}) : super( SettingsMetadataProviderRoute.name, initialChildren: children, @@ -874,18 +728,18 @@ class SettingsMetadataProviderRoute extends _i44.PageRouteInfo { static const String name = 'SettingsMetadataProviderRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i28.SettingsMetadataProviderPage(); + return const _i24.SettingsMetadataProviderPage(); }, ); } /// generated route for -/// [_i29.SettingsPage] -class SettingsRoute extends _i44.PageRouteInfo { - const SettingsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i25.SettingsPage] +class SettingsRoute extends _i40.PageRouteInfo { + const SettingsRoute({List<_i40.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -893,18 +747,18 @@ class SettingsRoute extends _i44.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i29.SettingsPage(); + return const _i25.SettingsPage(); }, ); } /// generated route for -/// [_i30.StatsAlbumsPage] -class StatsAlbumsRoute extends _i44.PageRouteInfo { - const StatsAlbumsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i26.StatsAlbumsPage] +class StatsAlbumsRoute extends _i40.PageRouteInfo { + const StatsAlbumsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -912,18 +766,18 @@ class StatsAlbumsRoute extends _i44.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i30.StatsAlbumsPage(); + return const _i26.StatsAlbumsPage(); }, ); } /// generated route for -/// [_i31.StatsArtistsPage] -class StatsArtistsRoute extends _i44.PageRouteInfo { - const StatsArtistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i27.StatsArtistsPage] +class StatsArtistsRoute extends _i40.PageRouteInfo { + const StatsArtistsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -931,18 +785,18 @@ class StatsArtistsRoute extends _i44.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i31.StatsArtistsPage(); + return const _i27.StatsArtistsPage(); }, ); } /// generated route for -/// [_i32.StatsMinutesPage] -class StatsMinutesRoute extends _i44.PageRouteInfo { - const StatsMinutesRoute({List<_i44.PageRouteInfo>? children}) +/// [_i28.StatsMinutesPage] +class StatsMinutesRoute extends _i40.PageRouteInfo { + const StatsMinutesRoute({List<_i40.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -950,18 +804,18 @@ class StatsMinutesRoute extends _i44.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i32.StatsMinutesPage(); + return const _i28.StatsMinutesPage(); }, ); } /// generated route for -/// [_i33.StatsPage] -class StatsRoute extends _i44.PageRouteInfo { - const StatsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i29.StatsPage] +class StatsRoute extends _i40.PageRouteInfo { + const StatsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -969,18 +823,18 @@ class StatsRoute extends _i44.PageRouteInfo { static const String name = 'StatsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i33.StatsPage(); + return const _i29.StatsPage(); }, ); } /// generated route for -/// [_i34.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i44.PageRouteInfo { - const StatsPlaylistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i30.StatsPlaylistsPage] +class StatsPlaylistsRoute extends _i40.PageRouteInfo { + const StatsPlaylistsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -988,18 +842,18 @@ class StatsPlaylistsRoute extends _i44.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i34.StatsPlaylistsPage(); + return const _i30.StatsPlaylistsPage(); }, ); } /// generated route for -/// [_i35.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i44.PageRouteInfo { - const StatsStreamFeesRoute({List<_i44.PageRouteInfo>? children}) +/// [_i31.StatsStreamFeesPage] +class StatsStreamFeesRoute extends _i40.PageRouteInfo { + const StatsStreamFeesRoute({List<_i40.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -1007,18 +861,18 @@ class StatsStreamFeesRoute extends _i44.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i35.StatsStreamFeesPage(); + return const _i31.StatsStreamFeesPage(); }, ); } /// generated route for -/// [_i36.StatsStreamsPage] -class StatsStreamsRoute extends _i44.PageRouteInfo { - const StatsStreamsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i32.StatsStreamsPage] +class StatsStreamsRoute extends _i40.PageRouteInfo { + const StatsStreamsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -1026,21 +880,21 @@ class StatsStreamsRoute extends _i44.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i36.StatsStreamsPage(); + return const _i32.StatsStreamsPage(); }, ); } /// generated route for -/// [_i37.TrackPage] -class TrackRoute extends _i44.PageRouteInfo { +/// [_i33.TrackPage] +class TrackRoute extends _i40.PageRouteInfo { TrackRoute({ - _i48.Key? key, + _i43.Key? key, required String trackId, - List<_i44.PageRouteInfo>? children, + List<_i40.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -1053,13 +907,13 @@ class TrackRoute extends _i44.PageRouteInfo { static const String name = 'TrackRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); - return _i37.TrackPage( + return _i33.TrackPage( key: args.key, trackId: args.trackId, ); @@ -1073,7 +927,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i48.Key? key; + final _i43.Key? key; final String trackId; @@ -1084,9 +938,9 @@ class TrackRouteArgs { } /// generated route for -/// [_i38.UserAlbumsPage] -class UserAlbumsRoute extends _i44.PageRouteInfo { - const UserAlbumsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i34.UserAlbumsPage] +class UserAlbumsRoute extends _i40.PageRouteInfo { + const UserAlbumsRoute({List<_i40.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -1094,18 +948,18 @@ class UserAlbumsRoute extends _i44.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i38.UserAlbumsPage(); + return const _i34.UserAlbumsPage(); }, ); } /// generated route for -/// [_i39.UserArtistsPage] -class UserArtistsRoute extends _i44.PageRouteInfo { - const UserArtistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i35.UserArtistsPage] +class UserArtistsRoute extends _i40.PageRouteInfo { + const UserArtistsRoute({List<_i40.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -1113,18 +967,18 @@ class UserArtistsRoute extends _i44.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i39.UserArtistsPage(); + return const _i35.UserArtistsPage(); }, ); } /// generated route for -/// [_i40.UserDownloadsPage] -class UserDownloadsRoute extends _i44.PageRouteInfo { - const UserDownloadsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i36.UserDownloadsPage] +class UserDownloadsRoute extends _i40.PageRouteInfo { + const UserDownloadsRoute({List<_i40.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -1132,18 +986,18 @@ class UserDownloadsRoute extends _i44.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i40.UserDownloadsPage(); + return const _i36.UserDownloadsPage(); }, ); } /// generated route for -/// [_i41.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i44.PageRouteInfo { - const UserLocalLibraryRoute({List<_i44.PageRouteInfo>? children}) +/// [_i37.UserLocalLibraryPage] +class UserLocalLibraryRoute extends _i40.PageRouteInfo { + const UserLocalLibraryRoute({List<_i40.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1151,18 +1005,18 @@ class UserLocalLibraryRoute extends _i44.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i41.UserLocalLibraryPage(); + return const _i37.UserLocalLibraryPage(); }, ); } /// generated route for -/// [_i42.UserPlaylistsPage] -class UserPlaylistsRoute extends _i44.PageRouteInfo { - const UserPlaylistsRoute({List<_i44.PageRouteInfo>? children}) +/// [_i38.UserPlaylistsPage] +class UserPlaylistsRoute extends _i40.PageRouteInfo { + const UserPlaylistsRoute({List<_i40.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1170,18 +1024,18 @@ class UserPlaylistsRoute extends _i44.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i42.UserPlaylistsPage(); + return const _i38.UserPlaylistsPage(); }, ); } /// generated route for -/// [_i43.WebViewLoginPage] -class WebViewLoginRoute extends _i44.PageRouteInfo { - const WebViewLoginRoute({List<_i44.PageRouteInfo>? children}) +/// [_i39.WebViewLoginPage] +class WebViewLoginRoute extends _i40.PageRouteInfo { + const WebViewLoginRoute({List<_i40.PageRouteInfo>? children}) : super( WebViewLoginRoute.name, initialChildren: children, @@ -1189,10 +1043,10 @@ class WebViewLoginRoute extends _i44.PageRouteInfo { static const String name = 'WebViewLoginRoute'; - static _i44.PageInfo page = _i44.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i43.WebViewLoginPage(); + return const _i39.WebViewLoginPage(); }, ); } diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index d9e31d78..2ad7554c 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -230,9 +230,9 @@ class TrackOptions extends HookConsumerWidget { onSelected: (value) async { switch (value) { case TrackOptionValue.album: - // await context.navigateTo( - // AlbumRoute(id: track.album!.id!, album: track.album!), - // ); + await context.navigateTo( + AlbumRoute(id: track.album.id, album: track.album), + ); break; case TrackOptionValue.delete: await File((track as SpotubeLocalTrackObject).path).delete(); diff --git a/lib/modules/stats/common/playlist_item.dart b/lib/modules/stats/common/playlist_item.dart index 8121f946..64abe7d5 100644 --- a/lib/modules/stats/common/playlist_item.dart +++ b/lib/modules/stats/common/playlist_item.dart @@ -1,4 +1,6 @@ +import 'package:auto_route/auto_route.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/ui/button_tile.dart'; import 'package:spotube/extensions/string.dart'; @@ -32,7 +34,7 @@ class StatsPlaylistItem extends StatelessWidget { ), trailing: info, onPressed: () { - // context.navigateTo(PlaylistRoute(id: playlist.id!, playlist: playlist)); + context.navigateTo(PlaylistRoute(id: playlist.id, playlist: playlist)); }, ); } diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index 11d06658..c3a9568a 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -55,11 +55,10 @@ class AlbumPage extends HookConsumerWidget { await tracksNotifier.fetchMore(); }, onFetchAll: () async { - // return tracksNotifier.fetchAll(); - return []; + return tracksNotifier.fetchAll(); }, onRefresh: () async { - // ref.invalidate(albumTracksProvider(album)); + ref.invalidate(metadataPluginAlbumTracksProvider(album.id)); }, ), routePath: "/album/${album.id}", diff --git a/lib/pages/track/track.dart b/lib/pages/track/track.dart index 128c5103..e55192e1 100644 --- a/lib/pages/track/track.dart +++ b/lib/pages/track/track.dart @@ -4,10 +4,12 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/fake.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/heart_button/heart_button.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/links/artist_link.dart'; +import 'package:spotube/components/links/link_text.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/components/track_tile/track_options.dart'; import 'package:spotube/extensions/context.dart'; @@ -140,16 +142,16 @@ class TrackPage extends HookConsumerWidget { children: [ const Icon(SpotubeIcons.album), const Gap(5), - // Flexible( - // child: LinkText( - // track.album!.name!, - // AlbumRoute( - // id: track.album!.id!, - // album: track.album!, - // ), - // push: true, - // ), - // ), + Flexible( + child: LinkText( + track.album.name, + AlbumRoute( + id: track.album.id, + album: track.album, + ), + push: true, + ), + ), ], ), const Gap(10), diff --git a/lib/provider/lyrics/synced.dart b/lib/provider/lyrics/synced.dart index 0c43d53e..de34005a 100644 --- a/lib/provider/lyrics/synced.dart +++ b/lib/provider/lyrics/synced.dart @@ -16,53 +16,6 @@ class SyncedLyricsNotifier extends FamilyAsyncNotifier { SpotubeTrackObject get _track => arg!; - // Future getSpotifyLyrics(String? token) async { - // final res = await globalDio.getUri( - // Uri.parse( - // "https://spclient.wg.spotify.com/color-lyrics/v2/track/${_track.id}?format=json&market=from_token", - // ), - // options: Options( - // headers: { - // "User-Agent": - // "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36", - // "App-platform": "WebPlayer", - // "authorization": "Bearer $token" - // }, - // responseType: ResponseType.json, - // validateStatus: (status) => true, - // ), - // ); - - // if (res.statusCode != 200) { - // return SubtitleSimple( - // lyrics: [], - // name: _track.name!, - // uri: res.realUri, - // rating: 0, - // provider: "Spotify", - // ); - // } - // final linesRaw = - // Map.castFrom(res.data)["lyrics"] - // ?["lines"] as List?; - - // final lines = linesRaw?.map((line) { - // return LyricSlice( - // time: Duration(milliseconds: int.parse(line["startTimeMs"])), - // text: line["words"] as String, - // ); - // }).toList() ?? - // []; - - // return SubtitleSimple( - // lyrics: lines, - // name: _track.name!, - // uri: res.realUri, - // rating: 100, - // provider: "Spotify", - // ); - // } - /// Lyrics credits: [lrclib.net](https://lrclib.net) and their contributors /// Thanks for their generous public API Future getLRCLibLyrics() async { From 86e55f7a3dfe895f455ff34765e2df7972b44be1 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 15:52:05 +0600 Subject: [PATCH 25/60] chore: fix connect freezed model not working --- lib/models/connect/connect.freezed.dart | 204 ++++++++++++++++++------ lib/models/connect/connect.g.dart | 6 +- lib/models/connect/load.dart | 12 +- lib/models/playback/track_sources.dart | 2 +- lib/pages/library/user_playlists.dart | 29 ++-- pubspec.lock | 31 ++-- pubspec.yaml | 10 +- 7 files changed, 202 insertions(+), 92 deletions(-) diff --git a/lib/models/connect/connect.freezed.dart b/lib/models/connect/connect.freezed.dart index ead14cf8..157d0911 100644 --- a/lib/models/connect/connect.freezed.dart +++ b/lib/models/connect/connect.freezed.dart @@ -33,36 +33,62 @@ WebSocketLoadEventData _$WebSocketLoadEventDataFromJson( /// @nodoc mixin _$WebSocketLoadEventData { - List get tracks => throw _privateConstructorUsedError; + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List get tracks => throw _privateConstructorUsedError; Object? get collection => throw _privateConstructorUsedError; int? get initialIndex => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ - required TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex) playlist, - required TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex) album, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult? Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, }) => throw _privateConstructorUsedError; @optionalTypeArgs TResult maybeWhen({ - TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, required TResult orElse(), }) => @@ -103,7 +129,11 @@ abstract class $WebSocketLoadEventDataCopyWith<$Res> { $Res Function(WebSocketLoadEventData) then) = _$WebSocketLoadEventDataCopyWithImpl<$Res, WebSocketLoadEventData>; @useResult - $Res call({List tracks, int? initialIndex}); + $Res call( + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + int? initialIndex}); } /// @nodoc @@ -129,7 +159,7 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res, tracks: null == tracks ? _value.tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, initialIndex: freezed == initialIndex ? _value.initialIndex : initialIndex // ignore: cast_nullable_to_non_nullable @@ -148,7 +178,9 @@ abstract class _$$WebSocketLoadEventDataPlaylistImplCopyWith<$Res> @override @useResult $Res call( - {List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, SpotubeSimplePlaylistObject? collection, int? initialIndex}); @@ -178,7 +210,7 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> tracks: null == tracks ? _value._tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, collection: freezed == collection ? _value.collection : collection // ignore: cast_nullable_to_non_nullable @@ -211,7 +243,9 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res> class _$WebSocketLoadEventDataPlaylistImpl extends WebSocketLoadEventDataPlaylist { _$WebSocketLoadEventDataPlaylistImpl( - {required final List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + required final List tracks, this.collection, this.initialIndex, final String? $type}) @@ -223,9 +257,11 @@ class _$WebSocketLoadEventDataPlaylistImpl Map json) => _$$WebSocketLoadEventDataPlaylistImplFromJson(json); - final List _tracks; + final List _tracks; @override - List get tracks { + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List get tracks { if (_tracks is EqualUnmodifiableListView) return _tracks; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tracks); @@ -274,11 +310,19 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult when({ - required TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex) playlist, - required TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex) album, }) { return playlist(tracks, collection, initialIndex); @@ -287,11 +331,19 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult? Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, }) { return playlist?.call(tracks, collection, initialIndex); @@ -300,11 +352,19 @@ class _$WebSocketLoadEventDataPlaylistImpl @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, required TResult orElse(), }) { @@ -355,7 +415,9 @@ class _$WebSocketLoadEventDataPlaylistImpl abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData { factory WebSocketLoadEventDataPlaylist( - {required final List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + required final List tracks, final SpotubeSimplePlaylistObject? collection, final int? initialIndex}) = _$WebSocketLoadEventDataPlaylistImpl; WebSocketLoadEventDataPlaylist._() : super._(); @@ -364,7 +426,9 @@ abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData { _$WebSocketLoadEventDataPlaylistImpl.fromJson; @override - List get tracks; + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List get tracks; @override SpotubeSimplePlaylistObject? get collection; @override @@ -389,7 +453,9 @@ abstract class _$$WebSocketLoadEventDataAlbumImplCopyWith<$Res> @override @useResult $Res call( - {List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, SpotubeSimpleAlbumObject? collection, int? initialIndex}); @@ -419,7 +485,7 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res> tracks: null == tracks ? _value._tracks : tracks // ignore: cast_nullable_to_non_nullable - as List, + as List, collection: freezed == collection ? _value.collection : collection // ignore: cast_nullable_to_non_nullable @@ -450,7 +516,9 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res> @JsonSerializable() class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { _$WebSocketLoadEventDataAlbumImpl( - {required final List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + required final List tracks, this.collection, this.initialIndex, final String? $type}) @@ -462,9 +530,11 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { Map json) => _$$WebSocketLoadEventDataAlbumImplFromJson(json); - final List _tracks; + final List _tracks; @override - List get tracks { + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List get tracks { if (_tracks is EqualUnmodifiableListView) return _tracks; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_tracks); @@ -512,11 +582,19 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult when({ - required TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex) playlist, - required TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex) + required TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex) album, }) { return album(tracks, collection, initialIndex); @@ -525,11 +603,19 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult? whenOrNull({ - TResult? Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult? Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult? Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, }) { return album?.call(tracks, collection, initialIndex); @@ -538,11 +624,19 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { @override @optionalTypeArgs TResult maybeWhen({ - TResult Function(List tracks, - SpotubeSimplePlaylistObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimplePlaylistObject? collection, + int? initialIndex)? playlist, - TResult Function(List tracks, - SpotubeSimpleAlbumObject? collection, int? initialIndex)? + TResult Function( + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List tracks, + SpotubeSimpleAlbumObject? collection, + int? initialIndex)? album, required TResult orElse(), }) { @@ -593,7 +687,9 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum { abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData { factory WebSocketLoadEventDataAlbum( - {required final List tracks, + {@Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + required final List tracks, final SpotubeSimpleAlbumObject? collection, final int? initialIndex}) = _$WebSocketLoadEventDataAlbumImpl; WebSocketLoadEventDataAlbum._() : super._(); @@ -602,7 +698,9 @@ abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData { _$WebSocketLoadEventDataAlbumImpl.fromJson; @override - List get tracks; + @Assert("tracks is List", + "tracks must be a list of SpotubeFullTrackObject") + List get tracks; @override SpotubeSimpleAlbumObject? get collection; @override diff --git a/lib/models/connect/connect.g.dart b/lib/models/connect/connect.g.dart index 465dc42e..2da8f9b0 100644 --- a/lib/models/connect/connect.g.dart +++ b/lib/models/connect/connect.g.dart @@ -10,7 +10,7 @@ _$WebSocketLoadEventDataPlaylistImpl _$$WebSocketLoadEventDataPlaylistImplFromJson(Map json) => _$WebSocketLoadEventDataPlaylistImpl( tracks: (json['tracks'] as List) - .map((e) => SpotubeFullTrackObject.fromJson( + .map((e) => SpotubeTrackObject.fromJson( Map.from(e as Map))) .toList(), collection: json['collection'] == null @@ -34,8 +34,8 @@ _$WebSocketLoadEventDataAlbumImpl _$$WebSocketLoadEventDataAlbumImplFromJson( Map json) => _$WebSocketLoadEventDataAlbumImpl( tracks: (json['tracks'] as List) - .map((e) => SpotubeFullTrackObject.fromJson( - Map.from(e as Map))) + .map((e) => + SpotubeTrackObject.fromJson(Map.from(e as Map))) .toList(), collection: json['collection'] == null ? null diff --git a/lib/models/connect/load.dart b/lib/models/connect/load.dart index 36d3c3ba..d61e0f1e 100644 --- a/lib/models/connect/load.dart +++ b/lib/models/connect/load.dart @@ -5,13 +5,21 @@ class WebSocketLoadEventData with _$WebSocketLoadEventData { const WebSocketLoadEventData._(); factory WebSocketLoadEventData.playlist({ - required List tracks, + @Assert( + "tracks is List", + "tracks must be a list of SpotubeFullTrackObject", + ) + required List tracks, SpotubeSimplePlaylistObject? collection, int? initialIndex, }) = WebSocketLoadEventDataPlaylist; factory WebSocketLoadEventData.album({ - required List tracks, + @Assert( + "tracks is List", + "tracks must be a list of SpotubeFullTrackObject", + ) + required List tracks, SpotubeSimpleAlbumObject? collection, int? initialIndex, }) = WebSocketLoadEventDataAlbum; diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index d894ff8b..1eb19a25 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -73,7 +73,7 @@ class TrackSource with _$TrackSource { } @JsonSerializable() -abstract class BasicSourcedTrack { +class BasicSourcedTrack { final TrackSourceQuery query; final AudioSource source; final TrackSourceInfo info; diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index d12c54b9..1963c7e8 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -7,7 +7,6 @@ import 'package:shadcn_flutter/shadcn_flutter.dart' hide Image; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; -import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/playbutton_view/playbutton_view.dart'; import 'package:spotube/models/metadata/metadata.dart'; @@ -61,14 +60,12 @@ class UserPlaylistsPage extends HookConsumerWidget { if (searchText.value.isEmpty) { return [ if (likedTracksPlaylist != null) likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items - as List?, + ...?playlistsQuery.asData?.value.items, ]; } return [ if (likedTracksPlaylist != null) likedTracksPlaylist, - ...?playlistsQuery.asData?.value.items - as List?, + ...?playlistsQuery.asData?.value.items, ] .map((e) => (weightedRatio(e.name, searchText.value), e)) .sorted((a, b) => b.$1.compareTo(a.$1)) @@ -116,19 +113,19 @@ class UserPlaylistsPage extends HookConsumerWidget { SliverPadding( padding: const EdgeInsets.symmetric(horizontal: 8), sliver: PlaybuttonView( - leading: Expanded( + leading: const Expanded( child: Row( children: [ - const PlaylistCreateDialogButton(), - const Gap(10), - Button.primary( - leading: const Icon(SpotubeIcons.magic), - child: Text(context.l10n.generate), - onPressed: () { - context.navigateTo(const PlaylistGeneratorRoute()); - }, - ), - const Gap(10), + PlaylistCreateDialogButton(), + // const Gap(10), + // Button.primary( + // leading: const Icon(SpotubeIcons.magic), + // child: Text(context.l10n.generate), + // onPressed: () { + // context.navigateTo(const PlaylistGeneratorRoute()); + // }, + // ), + // const Gap(10), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 909b8217..81c95843 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -983,34 +983,35 @@ packages: dependency: "direct main" description: name: flutter_secure_storage - sha256: "165164745e6afb5c0e3e3fcc72a012fb9e58496fb26ffb92cf22e16a821e85d0" + sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea" url: "https://pub.dev" source: hosted - version: "9.2.2" + version: "9.2.4" flutter_secure_storage_linux: - dependency: transitive + dependency: "direct overridden" description: - name: flutter_secure_storage_linux - sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" - url: "https://pub.dev" - source: hosted - version: "1.2.1" + path: flutter_secure_storage_linux + ref: patch-2 + resolved-ref: f076cbb65b075afd6e3b648122987a67306dc298 + url: "https://github.com/m-berto/flutter_secure_storage.git" + source: git + version: "2.0.1" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: "1693ab11121a5f925bbea0be725abfcfbbcf36c1e29e571f84a0c0f436147a81" + sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247" url: "https://pub.dev" source: hosted - version: "3.1.2" + version: "3.1.3" flutter_secure_storage_platform_interface: - dependency: transitive + dependency: "direct overridden" description: name: flutter_secure_storage_platform_interface - sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8 + sha256: b8337d3d52e429e6c0a7710e38cf9742a3bb05844bd927450eb94f80c11ef85d url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "2.0.0" flutter_secure_storage_web: dependency: transitive description: @@ -2516,10 +2517,10 @@ packages: dependency: "direct main" description: name: tray_manager - sha256: c2da0f0f1ddb455e721cf68d05d1281fec75cf5df0a1d3cb67b6ca0bdfd5709d + sha256: ad18c4cd73003097d182884bacb0578ad2865f3ab842a0ad00f6d043ed49eaf0 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.5.0" type_plus: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index e8cc7ef5..e0e2ee86 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,7 +62,7 @@ dependencies: sdk: flutter flutter_native_splash: ^2.4.6 flutter_riverpod: ^2.5.1 - flutter_secure_storage: ^9.0.0 + flutter_secure_storage: ^9.2.4 flutter_sharing_intent: ^1.1.0 flutter_undraw: ^0.2.1 form_builder_validators: ^11.1.1 @@ -119,7 +119,7 @@ dependencies: test: ^1.25.7 timezone: ^0.10.0 titlebar_buttons: ^1.0.0 - tray_manager: ^0.4.0 + tray_manager: ^0.5.0 url_launcher: ^6.2.6 uuid: ^4.4.0 version: ^3.0.2 @@ -187,6 +187,12 @@ dependency_overrides: flutter_svg: ^2.0.17 intl: any collection: any + flutter_secure_storage_linux: + git: + url: https://github.com/m-berto/flutter_secure_storage.git + ref: patch-2 + path: flutter_secure_storage_linux + flutter_secure_storage_platform_interface: 2.0.0 flutter: generate: true From 41cc79b5e6a57c5f66048b7b4a92cc0b86fbbafb Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 21:09:49 +0600 Subject: [PATCH 26/60] chore: make playback working --- drift_schemas/app_db/drift_schema_v7.json | 2 +- .../dialogs/track_details_dialog.dart | 14 ++++----- lib/components/track_tile/track_options.dart | 10 +++--- lib/models/database/database.g.dart | 26 ++++++++-------- lib/models/database/database.steps.dart | 4 +-- .../database/tables/audio_player_state.dart | 7 +++-- lib/models/playback/track_sources.dart | 23 +++++++++++--- .../playback/track_sources.freezed.dart | 8 +++-- lib/modules/playlist/playlist_card.dart | 4 +-- lib/provider/audio_player/audio_player.dart | 29 ++++++++++++----- lib/services/audio_player/audio_player.dart | 31 ++----------------- lib/services/metadata/endpoints/user.dart | 16 ++++++---- .../sourced_track/sources/youtube.dart | 17 +++++----- pubspec.lock | 4 +-- pubspec.yaml | 2 +- test/drift/app_db/generated/schema_v7.dart | 16 +++++----- 16 files changed, 111 insertions(+), 102 deletions(-) diff --git a/drift_schemas/app_db/drift_schema_v7.json b/drift_schemas/app_db/drift_schema_v7.json index da3f8713..97d1fa7e 100644 --- a/drift_schemas/app_db/drift_schema_v7.json +++ b/drift_schemas/app_db/drift_schema_v7.json @@ -1 +1 @@ -{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"tracks","getter_name":"tracks","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeTrackObjectListConverter()","dart_type_name":"List"}},{"name":"current_index","getter_name":"currentIndex","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entry_point","getter_name":"entryPoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"apis","getter_name":"apis","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"abilities","getter_name":"abilities","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":11,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file +{"_meta":{"description":"This file contains a serialized version of schema entities for drift.","version":"1.2.0"},"options":{"store_date_time_values_as_text":false},"entities":[{"id":0,"references":[],"type":"table","data":{"name":"authentication_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"cookie","getter_name":"cookie","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"access_token","getter_name":"accessToken","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}},{"name":"expiration","getter_name":"expiration","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":1,"references":[],"type":"table","data":{"name":"blacklist_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"element_type","getter_name":"elementType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(BlacklistedType.values)","dart_type_name":"BlacklistedType"}},{"name":"element_id","getter_name":"elementId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":2,"references":[],"type":"table","data":{"name":"preferences_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"audio_quality","getter_name":"audioQuality","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceQualities.high.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceQualities.values)","dart_type_name":"SourceQualities"}},{"name":"album_color_sync","getter_name":"albumColorSync","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"album_color_sync\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"album_color_sync\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"amoled_dark_theme","getter_name":"amoledDarkTheme","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"amoled_dark_theme\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"amoled_dark_theme\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"check_update","getter_name":"checkUpdate","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"check_update\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"check_update\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"normalize_audio","getter_name":"normalizeAudio","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"normalize_audio\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"normalize_audio\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"show_system_tray_icon","getter_name":"showSystemTrayIcon","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"show_system_tray_icon\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"show_system_tray_icon\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"system_title_bar","getter_name":"systemTitleBar","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"system_title_bar\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"system_title_bar\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"skip_non_music","getter_name":"skipNonMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"skip_non_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"skip_non_music\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"close_behavior","getter_name":"closeBehavior","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(CloseBehavior.close.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(CloseBehavior.values)","dart_type_name":"CloseBehavior"}},{"name":"accent_color_scheme","getter_name":"accentColorScheme","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"Orange:0xFFf97315\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeColorConverter()","dart_type_name":"SpotubeColor"}},{"name":"layout_mode","getter_name":"layoutMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(LayoutMode.adaptive.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(LayoutMode.values)","dart_type_name":"LayoutMode"}},{"name":"locale","getter_name":"locale","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant('{\"languageCode\":\"system\",\"countryCode\":\"system\"}')","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const LocaleConverter()","dart_type_name":"Locale"}},{"name":"market","getter_name":"market","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(Market.US.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(Market.values)","dart_type_name":"Market"}},{"name":"search_mode","getter_name":"searchMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SearchMode.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SearchMode.values)","dart_type_name":"SearchMode"}},{"name":"download_location","getter_name":"downloadLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[]},{"name":"local_library_location","getter_name":"localLibraryLocation","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"piped_instance","getter_name":"pipedInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://pipedapi.kavin.rocks\")","default_client_dart":null,"dsl_features":[]},{"name":"invidious_instance","getter_name":"invidiousInstance","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"https://inv.nadeko.net\")","default_client_dart":null,"dsl_features":[]},{"name":"theme_mode","getter_name":"themeMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(ThemeMode.system.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(ThemeMode.values)","dart_type_name":"ThemeMode"}},{"name":"audio_source","getter_name":"audioSource","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(AudioSource.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(AudioSource.values)","dart_type_name":"AudioSource"}},{"name":"youtube_client_engine","getter_name":"youtubeClientEngine","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(YoutubeClientEngine.youtubeExplode.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(YoutubeClientEngine.values)","dart_type_name":"YoutubeClientEngine"}},{"name":"stream_music_codec","getter_name":"streamMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.weba.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"download_music_codec","getter_name":"downloadMusicCodec","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceCodecs.m4a.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceCodecs.values)","dart_type_name":"SourceCodecs"}},{"name":"discord_presence","getter_name":"discordPresence","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"discord_presence\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"discord_presence\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"endless_playback","getter_name":"endlessPlayback","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"endless_playback\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"endless_playback\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]},{"name":"enable_connect","getter_name":"enableConnect","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"enable_connect\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"enable_connect\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]},{"name":"connect_port","getter_name":"connectPort","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(-1)","default_client_dart":null,"dsl_features":[]},{"name":"cache_music","getter_name":"cacheMusic","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"cache_music\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"cache_music\" IN (0, 1))"},"default_dart":"const Constant(true)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":3,"references":[],"type":"table","data":{"name":"scrobbler_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"username","getter_name":"username","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"password_hash","getter_name":"passwordHash","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"EncryptedTextConverter()","dart_type_name":"DecryptedText"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":4,"references":[],"type":"table","data":{"name":"skip_segment_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"start","getter_name":"start","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"end","getter_name":"end","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":5,"references":[],"type":"table","data":{"name":"source_match_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_id","getter_name":"sourceId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"source_type","getter_name":"sourceType","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"Constant(SourceType.youtube.name)","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(SourceType.values)","dart_type_name":"SourceType"}},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":6,"references":[],"type":"table","data":{"name":"audio_player_state_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"playing","getter_name":"playing","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"playing\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"playing\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"loop_mode","getter_name":"loopMode","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(PlaylistMode.values)","dart_type_name":"PlaylistMode"}},{"name":"shuffled","getter_name":"shuffled","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"shuffled\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"shuffled\" IN (0, 1))"},"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"collections","getter_name":"collections","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"tracks","getter_name":"tracks","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":"const Constant(\"[]\")","default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const SpotubeTrackObjectListConverter()","dart_type_name":"List"}},{"name":"current_index","getter_name":"currentIndex","moor_type":"int","nullable":false,"customConstraints":null,"default_dart":"const Constant(0)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":7,"references":[],"type":"table","data":{"name":"history_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"created_at","getter_name":"createdAt","moor_type":"dateTime","nullable":false,"customConstraints":null,"default_dart":"currentDateAndTime","default_client_dart":null,"dsl_features":[]},{"name":"type","getter_name":"type","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const EnumNameConverter(HistoryEntryType.values)","dart_type_name":"HistoryEntryType"}},{"name":"item_id","getter_name":"itemId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const MapTypeConverter()","dart_type_name":"Map"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":8,"references":[],"type":"table","data":{"name":"lyrics_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"track_id","getter_name":"trackId","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"data","getter_name":"data","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"SubtitleTypeConverter()","dart_type_name":"SubtitleSimple"}}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":9,"references":[],"type":"table","data":{"name":"metadata_plugins_table","was_declared_in_moor":false,"columns":[{"name":"id","getter_name":"id","moor_type":"int","nullable":false,"customConstraints":null,"defaultConstraints":"PRIMARY KEY AUTOINCREMENT","dialectAwareDefaultConstraints":{"sqlite":"PRIMARY KEY AUTOINCREMENT"},"default_dart":null,"default_client_dart":null,"dsl_features":["auto-increment"]},{"name":"name","getter_name":"name","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[{"allowed-lengths":{"min":1,"max":50}}]},{"name":"description","getter_name":"description","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"version","getter_name":"version","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"author","getter_name":"author","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"entry_point","getter_name":"entryPoint","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[]},{"name":"apis","getter_name":"apis","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"abilities","getter_name":"abilities","moor_type":"string","nullable":false,"customConstraints":null,"default_dart":null,"default_client_dart":null,"dsl_features":[],"type_converter":{"dart_expr":"const StringListConverter()","dart_type_name":"List"}},{"name":"selected","getter_name":"selected","moor_type":"bool","nullable":false,"customConstraints":null,"defaultConstraints":"CHECK (\"selected\" IN (0, 1))","dialectAwareDefaultConstraints":{"sqlite":"CHECK (\"selected\" IN (0, 1))"},"default_dart":"const Constant(false)","default_client_dart":null,"dsl_features":[]}],"is_virtual":false,"without_rowid":false,"constraints":[]}},{"id":10,"references":[1],"type":"index","data":{"on":1,"name":"unique_blacklist","sql":null,"unique":true,"columns":["element_type","element_id"]}},{"id":11,"references":[5],"type":"index","data":{"on":5,"name":"uniq_track_match","sql":null,"unique":true,"columns":["track_id","source_id","source_type"]}}]} \ No newline at end of file diff --git a/lib/components/dialogs/track_details_dialog.dart b/lib/components/dialogs/track_details_dialog.dart index f916aefb..4e686c06 100644 --- a/lib/components/dialogs/track_details_dialog.dart +++ b/lib/components/dialogs/track_details_dialog.dart @@ -9,7 +9,6 @@ import 'package:spotube/extensions/duration.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/playback/track_sources.dart'; import 'package:spotube/provider/server/track_sources.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; class TrackDetailsDialog extends HookConsumerWidget { final SpotubeFullTrackObject track; @@ -59,12 +58,13 @@ class TrackDetailsDialog extends HookConsumerWidget { overflow: TextOverflow.ellipsis, ), context.l10n.channel: Text(sourceInfo.artists), - context.l10n.streamUrl: Hyperlink( - (track as SourcedTrack).url, - (track as SourcedTrack).url, - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), + if (sourcedTrack.asData?.value.url != null) + context.l10n.streamUrl: Hyperlink( + sourcedTrack.asData!.value.url, + sourcedTrack.asData!.value.url, + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), }; return AlertDialog( diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 2ad7554c..22bdad78 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -214,14 +214,12 @@ class TrackOptions extends HookConsumerWidget { ]); final progressNotifier = useMemoized(() { - if (track is! SpotubeFullTrackObject) { - return throw Exception( - "Invalid usage of `progressNotifierFuture`. Track must be a SpotubeFullTrackObject to get download progress", - ); + if (track is SpotubeLocalTrackObject) { + return null; } return downloadManager .getProgressNotifier(track as SpotubeFullTrackObject); - }); + }, [downloadManager, track]); final isLocalTrack = track is SpotubeLocalTrackObject; @@ -346,7 +344,7 @@ class TrackOptions extends HookConsumerWidget { } }, icon: icon ?? const Icon(SpotubeIcons.moreHorizontal), - variance: ButtonVariance.outline, + variance: ButtonVariance.ghost, headings: [ Basic( leading: AspectRatio( diff --git a/lib/models/database/database.g.dart b/lib/models/database/database.g.dart index 35905ce4..23b2e648 100644 --- a/lib/models/database/database.g.dart +++ b/lib/models/database/database.g.dart @@ -2930,7 +2930,9 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable @override late final GeneratedColumnWithTypeConverter, String> tracks = GeneratedColumn('tracks', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true) + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("[]")) .withConverter>( $AudioPlayerStateTableTable.$convertertracks); static const VerificationMeta _currentIndexMeta = @@ -2938,7 +2940,9 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable @override late final GeneratedColumn currentIndex = GeneratedColumn( 'current_index', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); @override List get $columns => [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; @@ -2976,8 +2980,6 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable _currentIndexMeta, currentIndex.isAcceptableOrUnknown( data['current_index']!, _currentIndexMeta)); - } else if (isInserting) { - context.missing(_currentIndexMeta); } return context; } @@ -3189,14 +3191,12 @@ class AudioPlayerStateTableCompanion required PlaylistMode loopMode, required bool shuffled, required List collections, - required List tracks, - required int currentIndex, + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), }) : playing = Value(playing), loopMode = Value(loopMode), shuffled = Value(shuffled), - collections = Value(collections), - tracks = Value(tracks), - currentIndex = Value(currentIndex); + collections = Value(collections); static Insertable custom({ Expression? id, Expression? playing, @@ -5751,8 +5751,8 @@ typedef $$AudioPlayerStateTableTableCreateCompanionBuilder required PlaylistMode loopMode, required bool shuffled, required List collections, - required List tracks, - required int currentIndex, + Value> tracks, + Value currentIndex, }); typedef $$AudioPlayerStateTableTableUpdateCompanionBuilder = AudioPlayerStateTableCompanion Function({ @@ -5922,8 +5922,8 @@ class $$AudioPlayerStateTableTableTableManager extends RootTableManager< required PlaylistMode loopMode, required bool shuffled, required List collections, - required List tracks, - required int currentIndex, + Value> tracks = const Value.absent(), + Value currentIndex = const Value.absent(), }) => AudioPlayerStateTableCompanion.insert( id: id, diff --git a/lib/models/database/database.steps.dart b/lib/models/database/database.steps.dart index 1bfff438..93765c5c 100644 --- a/lib/models/database/database.steps.dart +++ b/lib/models/database/database.steps.dart @@ -1921,10 +1921,10 @@ class Shape14 extends i0.VersionedTable { i1.GeneratedColumn _column_57(String aliasedName) => i1.GeneratedColumn('tracks', aliasedName, false, - type: i1.DriftSqlType.string); + type: i1.DriftSqlType.string, defaultValue: const Constant("[]")); i1.GeneratedColumn _column_58(String aliasedName) => i1.GeneratedColumn('current_index', aliasedName, false, - type: i1.DriftSqlType.int); + type: i1.DriftSqlType.int, defaultValue: const Constant(0)); class Shape15 extends i0.VersionedTable { Shape15({required super.source, required super.alias}) : super.aliased(); diff --git a/lib/models/database/tables/audio_player_state.dart b/lib/models/database/tables/audio_player_state.dart index 7cb62709..cf47eb04 100644 --- a/lib/models/database/tables/audio_player_state.dart +++ b/lib/models/database/tables/audio_player_state.dart @@ -6,9 +6,10 @@ class AudioPlayerStateTable extends Table { TextColumn get loopMode => textEnum()(); BoolColumn get shuffled => boolean()(); TextColumn get collections => text().map(const StringListConverter())(); - TextColumn get tracks => - text().map(const SpotubeTrackObjectListConverter())(); - IntColumn get currentIndex => integer()(); + TextColumn get tracks => text() + .map(const SpotubeTrackObjectListConverter()) + .withDefault(const Constant("[]"))(); + IntColumn get currentIndex => integer().withDefault(const Constant(0))(); } class SpotubeTrackObjectListConverter diff --git a/lib/models/playback/track_sources.dart b/lib/models/playback/track_sources.dart index 1eb19a25..e957cc17 100644 --- a/lib/models/playback/track_sources.dart +++ b/lib/models/playback/track_sources.dart @@ -9,6 +9,8 @@ part 'track_sources.g.dart'; @freezed class TrackSourceQuery with _$TrackSourceQuery { + TrackSourceQuery._(); + factory TrackSourceQuery({ required String id, required String title, @@ -37,10 +39,23 @@ class TrackSourceQuery with _$TrackSourceQuery { /// Parses [SpotubeMedia]'s [uri] property to create a [TrackSourceQuery]. factory TrackSourceQuery.parseUri(String url) { final uri = Uri.parse(url); - return TrackSourceQuery.fromJson({ - "id": uri.pathSegments.last, - ...uri.queryParameters, - }); + return TrackSourceQuery( + id: uri.pathSegments.last, + title: uri.queryParameters['title'] ?? '', + artists: uri.queryParameters['artists']?.split(',') ?? [], + album: uri.queryParameters['album'] ?? '', + durationMs: int.tryParse(uri.queryParameters['durationMs'] ?? '0') ?? 0, + isrc: uri.queryParameters['isrc'] ?? '', + explicit: uri.queryParameters['explicit']?.toLowerCase() == 'true', + ); + } + + String queryString() { + return toJson() + .entries + .map((e) => + "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") + .join("&"); } } diff --git a/lib/models/playback/track_sources.freezed.dart b/lib/models/playback/track_sources.freezed.dart index 1f78d2b3..28130873 100644 --- a/lib/models/playback/track_sources.freezed.dart +++ b/lib/models/playback/track_sources.freezed.dart @@ -184,7 +184,7 @@ class __$$TrackSourceQueryImplCopyWithImpl<$Res> /// @nodoc @JsonSerializable() -class _$TrackSourceQueryImpl implements _TrackSourceQuery { +class _$TrackSourceQueryImpl extends _TrackSourceQuery { _$TrackSourceQueryImpl( {required this.id, required this.title, @@ -193,7 +193,8 @@ class _$TrackSourceQueryImpl implements _TrackSourceQuery { required this.durationMs, required this.isrc, required this.explicit}) - : _artists = artists; + : _artists = artists, + super._(); factory _$TrackSourceQueryImpl.fromJson(Map json) => _$$TrackSourceQueryImplFromJson(json); @@ -269,7 +270,7 @@ class _$TrackSourceQueryImpl implements _TrackSourceQuery { } } -abstract class _TrackSourceQuery implements TrackSourceQuery { +abstract class _TrackSourceQuery extends TrackSourceQuery { factory _TrackSourceQuery( {required final String id, required final String title, @@ -278,6 +279,7 @@ abstract class _TrackSourceQuery implements TrackSourceQuery { required final int durationMs, required final String isrc, required final bool explicit}) = _$TrackSourceQueryImpl; + _TrackSourceQuery._() : super._(); factory _TrackSourceQuery.fromJson(Map json) = _$TrackSourceQueryImpl.fromJson; diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index 36127792..811b9332 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -62,10 +62,10 @@ class PlaylistCard extends HookConsumerWidget { } Future> fetchAllTracks() async { - final initialTracks = await fetchInitialTracks(); + await fetchInitialTracks(); if (playlist.id == 'user-liked-tracks') { - return initialTracks; + return ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll(); } return ref diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 1432e5e3..075a93fe 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -2,7 +2,6 @@ import 'dart:math'; import 'package:drift/drift.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:media_kit/media_kit.dart' hide Track; import 'package:spotube/extensions/list.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; @@ -48,8 +47,8 @@ class AudioPlayerNotifier extends Notifier { loopMode: audioPlayer.loopMode, shuffled: audioPlayer.isShuffled, collections: [], - tracks: [], - currentIndex: 0, + tracks: const Value([]), + currentIndex: const Value(0), id: const Value(0), ), ); @@ -143,10 +142,12 @@ class AudioPlayerNotifier extends Notifier { final queries = playlist.medias .map((media) => TrackSourceQuery.parseUri(media.uri)) .toList(); + final tracks = queries - .map((query) => state.tracks.firstWhere( - (element) => element.id == query.id, - )) + .map( + (query) => state.tracks + .firstWhere((element) => element.id == query.id), + ) .toList(); state = state.copyWith( tracks: tracks, @@ -249,6 +250,10 @@ class AudioPlayerNotifier extends Notifier { if (_blacklist.contains(track)) return; if (state.tracks.any((element) => _compareTracks(element, track))) return; + + state = state.copyWith( + tracks: [...state.tracks, track], + ); await audioPlayer.addTrack(SpotubeMedia(track)); } @@ -256,6 +261,9 @@ class AudioPlayerNotifier extends Notifier { _assertAllowedTracks(tracks); tracks = _blacklist.filter(tracks).toList(); + state = state.copyWith( + tracks: [...state.tracks, ...tracks], + ); for (final track in tracks) { await audioPlayer.addTrack(SpotubeMedia(track)); } @@ -313,10 +321,15 @@ class AudioPlayerNotifier extends Notifier { if (medias.isEmpty) return; - await removeCollections(state.collections); + state = state.copyWith( + // These are filtered tracks as well + tracks: medias.map((media) => media.track).toList(), + currentIndex: initialIndex, + collections: [], + ); await audioPlayer.openPlaylist( - medias.map((s) => s as Media).toList(), + medias, initialIndex: initialIndex, autoPlay: autoPlay, ); diff --git a/lib/services/audio_player/audio_player.dart b/lib/services/audio_player/audio_player.dart index 4deaf720..93a6417e 100644 --- a/lib/services/audio_player/audio_player.dart +++ b/lib/services/audio_player/audio_player.dart @@ -19,8 +19,6 @@ part 'audio_player_impl.dart'; class SpotubeMedia extends mk.Media { static int serverPort = 0; - final SpotubeTrackObject track; - static String get _host => kIsWindows ? "localhost" : InternetAddress.anyIPv4.address; @@ -29,10 +27,11 @@ class SpotubeMedia extends mk.Media { return params.entries .map((e) => - "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value)}") + "${Uri.encodeComponent(e.key)}=${Uri.encodeComponent(e.value is List ? e.value.join(",") : e.value.toString())}") .join("&"); } + final SpotubeTrackObject track; SpotubeMedia( this.track, { Map? extras, @@ -47,32 +46,6 @@ class SpotubeMedia extends mk.Media { ? track.path : "http://$_host:$serverPort/stream/${track.id}?${_queries(track as SpotubeFullTrackObject)}", ); - - @override - String get uri { - return switch (track) { - /// [super.uri] must be used instead of [track.path] to prevent wrong - /// path format exceptions in Windows causing [extras] to be null - SpotubeLocalTrackObject() => super.uri, - _ => "http://$_host:" - "$serverPort/stream/${track.id}", - }; - } - - // @override - // operator ==(Object other) { - // if (other is! SpotubeMedia) return false; - - // final isLocal = track is LocalTrack && other.track is LocalTrack; - // return isLocal - // ? (other.track as LocalTrack).path == (track as LocalTrack).path - // : other.track.id == track.id; - // } - - // @override - // int get hashCode => track is LocalTrack - // ? (track as LocalTrack).path.hashCode - // : track.id.hashCode; } abstract class AudioPlayerInterface { diff --git a/lib/services/metadata/endpoints/user.dart b/lib/services/metadata/endpoints/user.dart index 11ac3f9b..3c8f0e42 100644 --- a/lib/services/metadata/endpoints/user.dart +++ b/lib/services/metadata/endpoints/user.dart @@ -106,23 +106,27 @@ class MetadataPluginUserEndpoint { } Future> isSavedTracks(List ids) async { - return await hetuMetadataUser.invoke( + final values = await hetuMetadataUser.invoke( "isSavedTracks", positionalArgs: [ids], - ) as List; + ); + return (values as List).cast(); } Future> isSavedAlbums(List ids) async { - return await hetuMetadataUser.invoke( + final values = await hetuMetadataUser.invoke( "isSavedAlbums", positionalArgs: [ids], - ) as List; + ) as List; + return values.cast(); } Future> isSavedArtists(List ids) async { - return await hetuMetadataUser.invoke( + final values = await hetuMetadataUser.invoke( "isSavedArtists", positionalArgs: [ids], - ) as List; + ) as List; + + return values.cast(); } } diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index f55e4337..925a48e2 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -109,14 +109,15 @@ class YoutubeSourcedTrack extends SourcedTrack { return manifest.audioOnly.map((streamInfo) { return TrackSource( url: streamInfo.url.toString(), - quality: streamInfo.qualityLabel == "AUDIO_QUALITY_HIGH" - ? SourceQualities.high - : streamInfo.qualityLabel == "AUDIO_QUALITY_MEDIUM" - ? SourceQualities.medium - : SourceQualities.low, - codec: streamInfo.codec.mimeType == "audio/mp4" - ? SourceCodecs.m4a - : SourceCodecs.weba, + quality: switch (streamInfo.qualityLabel) { + "medium" => SourceQualities.medium, + "high" => SourceQualities.high, + "low" => SourceQualities.low, + _ => SourceQualities.high, + }, + codec: streamInfo.codec.mimeType == "audio/webm" + ? SourceCodecs.weba + : SourceCodecs.m4a, bitrate: streamInfo.bitrate.bitsPerSecond.toString(), ); }).toList(); diff --git a/pubspec.lock b/pubspec.lock index 81c95843..0347baf5 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -2813,10 +2813,10 @@ packages: dependency: "direct main" description: name: youtube_explode_dart - sha256: "3e1f1b5aa575670afc9dbc96cece23af78f9ec2044ce0d9f70d136fff6c53b53" + sha256: "8db47e0f947598f6aa29d2862efb98b92af0c78990d4b23c224f3475c556b47b" url: "https://pub.dev" source: hosted - version: "2.4.0-dev.1" + version: "2.4.2" yt_dlp_dart: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index e0e2ee86..8ae9ec8b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -129,7 +129,7 @@ dependencies: wikipedia_api: ^0.1.0 win32_registry: ^1.1.5 window_manager: ^0.4.3 - youtube_explode_dart: ^2.4.0-dev.1 + youtube_explode_dart: ^2.4.2 yt_dlp_dart: git: url: https://github.com/KRTirtho/yt_dlp_dart.git diff --git a/test/drift/app_db/generated/schema_v7.dart b/test/drift/app_db/generated/schema_v7.dart index 38a8e815..661c9a18 100644 --- a/test/drift/app_db/generated/schema_v7.dart +++ b/test/drift/app_db/generated/schema_v7.dart @@ -2301,10 +2301,14 @@ class AudioPlayerStateTable extends Table type: DriftSqlType.string, requiredDuringInsert: true); late final GeneratedColumn tracks = GeneratedColumn( 'tracks', aliasedName, false, - type: DriftSqlType.string, requiredDuringInsert: true); + type: DriftSqlType.string, + requiredDuringInsert: false, + defaultValue: const Constant("[]")); late final GeneratedColumn currentIndex = GeneratedColumn( 'current_index', aliasedName, false, - type: DriftSqlType.int, requiredDuringInsert: true); + type: DriftSqlType.int, + requiredDuringInsert: false, + defaultValue: const Constant(0)); @override List get $columns => [id, playing, loopMode, shuffled, collections, tracks, currentIndex]; @@ -2499,14 +2503,12 @@ class AudioPlayerStateTableCompanion required String loopMode, required bool shuffled, required String collections, - required String tracks, - required int currentIndex, + this.tracks = const Value.absent(), + this.currentIndex = const Value.absent(), }) : playing = Value(playing), loopMode = Value(loopMode), shuffled = Value(shuffled), - collections = Value(collections), - tracks = Value(tracks), - currentIndex = Value(currentIndex); + collections = Value(collections); static Insertable custom({ Expression? id, Expression? playing, From aa65bf291d461a2786696227826dccb7ec266b04 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 22:32:22 +0600 Subject: [PATCH 27/60] refactor: use metadata plugin authentication instead --- lib/collections/routes.dart | 12 +- lib/collections/routes.gr.dart | 308 ++++++++--------- .../fallbacks/anonymous_fallback.dart | 6 +- lib/components/heart_button/heart_button.dart | 6 +- .../track_presentation/presentation_top.dart | 1 + lib/components/track_tile/track_options.dart | 10 +- .../configurators/use_endless_playback.dart | 1 - lib/modules/home/sections/new_releases.dart | 6 +- lib/modules/player/player.dart | 9 +- lib/modules/player/player_actions.dart | 23 +- lib/modules/root/sidebar/sidebar_footer.dart | 6 +- lib/pages/album/album.dart | 2 +- lib/pages/artist/section/header.dart | 6 +- .../getting_started/sections/support.dart | 28 -- lib/pages/library/user_albums.dart | 1 - lib/pages/library/user_playlists.dart | 2 +- .../mobile_login/hooks/login_callback.dart | 81 ----- lib/pages/mobile_login/mobile_login.dart | 80 ----- .../no_webview_runtime_dialog.dart | 57 ---- lib/pages/search/search.dart | 3 +- .../authentication/authentication.dart | 309 ------------------ .../metadata_plugin/library/tracks.dart | 13 +- 22 files changed, 192 insertions(+), 778 deletions(-) delete mode 100644 lib/pages/mobile_login/hooks/login_callback.dart delete mode 100644 lib/pages/mobile_login/mobile_login.dart delete mode 100644 lib/pages/mobile_login/no_webview_runtime_dialog.dart delete mode 100644 lib/provider/authentication/authentication.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 526da1c9..19a63587 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; final rootNavigatorKey = GlobalKey(); @@ -28,9 +28,10 @@ class AppRouter extends RootStackRouter { guards: [ AutoRouteGuardCallback( (resolver, router) async { - final auth = await ref.read(authenticationProvider.future); + final authenticated = await ref + .read(metadataPluginAuthenticatedProvider.future); - if (auth == null && !KVStoreService.doneGettingStarted) { + if (!authenticated && !KVStoreService.doneGettingStarted) { resolver.redirect(const GettingStartedRoute()); } else { resolver.next(true); @@ -209,11 +210,6 @@ class AppRouter extends RootStackRouter { page: GettingStartedRoute.page, // parentNavigatorKey: rootNavigatorKey, ), - AutoRoute( - path: "/login", - page: WebViewLoginRoute.page, - // parentNavigatorKey: rootNavigatorKey, - ), AutoRoute( path: "/lastfm-login", page: LastFMLoginRoute.page, diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index a3746de1..d998e45a 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,10 +8,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i40; -import 'package:flutter/material.dart' as _i41; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i43; -import 'package:spotube/models/metadata/metadata.dart' as _i42; +import 'package:auto_route/auto_route.dart' as _i39; +import 'package:flutter/material.dart' as _i40; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i42; +import 'package:spotube/models/metadata/metadata.dart' as _i41; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -31,7 +31,6 @@ import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' import 'package:spotube/pages/library/user_playlists.dart' as _i38; import 'package:spotube/pages/lyrics/lyrics.dart' as _i15; import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i16; -import 'package:spotube/pages/mobile_login/mobile_login.dart' as _i39; import 'package:spotube/pages/player/lyrics.dart' as _i17; import 'package:spotube/pages/player/queue.dart' as _i18; import 'package:spotube/pages/player/sources.dart' as _i19; @@ -56,8 +55,8 @@ import 'package:spotube/pages/track/track.dart' as _i33; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i40.PageRouteInfo { - const AboutSpotubeRoute({List<_i40.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i39.PageRouteInfo { + const AboutSpotubeRoute({List<_i39.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -65,7 +64,7 @@ class AboutSpotubeRoute extends _i40.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -75,12 +74,12 @@ class AboutSpotubeRoute extends _i40.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i40.PageRouteInfo { +class AlbumRoute extends _i39.PageRouteInfo { AlbumRoute({ - _i41.Key? key, + _i40.Key? key, required String id, - required _i42.SpotubeSimpleAlbumObject album, - List<_i40.PageRouteInfo>? children, + required _i41.SpotubeSimpleAlbumObject album, + List<_i39.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -94,7 +93,7 @@ class AlbumRoute extends _i40.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -114,11 +113,11 @@ class AlbumRouteArgs { required this.album, }); - final _i41.Key? key; + final _i40.Key? key; final String id; - final _i42.SpotubeSimpleAlbumObject album; + final _i41.SpotubeSimpleAlbumObject album; @override String toString() { @@ -128,11 +127,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i40.PageRouteInfo { +class ArtistRoute extends _i39.PageRouteInfo { ArtistRoute({ required String artistId, - _i41.Key? key, - List<_i40.PageRouteInfo>? children, + _i40.Key? key, + List<_i39.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -145,7 +144,7 @@ class ArtistRoute extends _i40.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -167,7 +166,7 @@ class ArtistRouteArgs { final String artistId; - final _i41.Key? key; + final _i40.Key? key; @override String toString() { @@ -177,8 +176,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i40.PageRouteInfo { - const BlackListRoute({List<_i40.PageRouteInfo>? children}) +class BlackListRoute extends _i39.PageRouteInfo { + const BlackListRoute({List<_i39.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -186,7 +185,7 @@ class BlackListRoute extends _i40.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -196,8 +195,8 @@ class BlackListRoute extends _i40.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i40.PageRouteInfo { - const ConnectControlRoute({List<_i40.PageRouteInfo>? children}) +class ConnectControlRoute extends _i39.PageRouteInfo { + const ConnectControlRoute({List<_i39.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -205,7 +204,7 @@ class ConnectControlRoute extends _i40.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -215,8 +214,8 @@ class ConnectControlRoute extends _i40.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i40.PageRouteInfo { - const ConnectRoute({List<_i40.PageRouteInfo>? children}) +class ConnectRoute extends _i39.PageRouteInfo { + const ConnectRoute({List<_i39.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -224,7 +223,7 @@ class ConnectRoute extends _i40.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -234,8 +233,8 @@ class ConnectRoute extends _i40.PageRouteInfo { /// generated route for /// [_i7.GettingStartedPage] -class GettingStartedRoute extends _i40.PageRouteInfo { - const GettingStartedRoute({List<_i40.PageRouteInfo>? children}) +class GettingStartedRoute extends _i39.PageRouteInfo { + const GettingStartedRoute({List<_i39.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -243,7 +242,7 @@ class GettingStartedRoute extends _i40.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i7.GettingStartedPage(); @@ -254,12 +253,12 @@ class GettingStartedRoute extends _i40.PageRouteInfo { /// generated route for /// [_i8.HomeBrowseSectionItemsPage] class HomeBrowseSectionItemsRoute - extends _i40.PageRouteInfo { + extends _i39.PageRouteInfo { HomeBrowseSectionItemsRoute({ - _i43.Key? key, + _i42.Key? key, required String sectionId, - required _i42.SpotubeBrowseSectionObject section, - List<_i40.PageRouteInfo>? children, + required _i41.SpotubeBrowseSectionObject section, + List<_i39.PageRouteInfo>? children, }) : super( HomeBrowseSectionItemsRoute.name, args: HomeBrowseSectionItemsRouteArgs( @@ -273,7 +272,7 @@ class HomeBrowseSectionItemsRoute static const String name = 'HomeBrowseSectionItemsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -293,11 +292,11 @@ class HomeBrowseSectionItemsRouteArgs { required this.section, }); - final _i43.Key? key; + final _i42.Key? key; final String sectionId; - final _i42.SpotubeBrowseSectionObject section; + final _i41.SpotubeBrowseSectionObject section; @override String toString() { @@ -307,8 +306,8 @@ class HomeBrowseSectionItemsRouteArgs { /// generated route for /// [_i9.HomePage] -class HomeRoute extends _i40.PageRouteInfo { - const HomeRoute({List<_i40.PageRouteInfo>? children}) +class HomeRoute extends _i39.PageRouteInfo { + const HomeRoute({List<_i39.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -316,7 +315,7 @@ class HomeRoute extends _i40.PageRouteInfo { static const String name = 'HomeRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i9.HomePage(); @@ -326,8 +325,8 @@ class HomeRoute extends _i40.PageRouteInfo { /// generated route for /// [_i10.LastFMLoginPage] -class LastFMLoginRoute extends _i40.PageRouteInfo { - const LastFMLoginRoute({List<_i40.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i39.PageRouteInfo { + const LastFMLoginRoute({List<_i39.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -335,7 +334,7 @@ class LastFMLoginRoute extends _i40.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i10.LastFMLoginPage(); @@ -345,8 +344,8 @@ class LastFMLoginRoute extends _i40.PageRouteInfo { /// generated route for /// [_i11.LibraryPage] -class LibraryRoute extends _i40.PageRouteInfo { - const LibraryRoute({List<_i40.PageRouteInfo>? children}) +class LibraryRoute extends _i39.PageRouteInfo { + const LibraryRoute({List<_i39.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -354,7 +353,7 @@ class LibraryRoute extends _i40.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i11.LibraryPage(); @@ -364,11 +363,11 @@ class LibraryRoute extends _i40.PageRouteInfo { /// generated route for /// [_i12.LikedPlaylistPage] -class LikedPlaylistRoute extends _i40.PageRouteInfo { +class LikedPlaylistRoute extends _i39.PageRouteInfo { LikedPlaylistRoute({ - _i41.Key? key, - required _i42.SpotubeSimplePlaylistObject playlist, - List<_i40.PageRouteInfo>? children, + _i40.Key? key, + required _i41.SpotubeSimplePlaylistObject playlist, + List<_i39.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -380,7 +379,7 @@ class LikedPlaylistRoute extends _i40.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -398,9 +397,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i41.Key? key; + final _i40.Key? key; - final _i42.SpotubeSimplePlaylistObject playlist; + final _i41.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -410,13 +409,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i13.LocalLibraryPage] -class LocalLibraryRoute extends _i40.PageRouteInfo { +class LocalLibraryRoute extends _i39.PageRouteInfo { LocalLibraryRoute({ required String location, - _i41.Key? key, + _i40.Key? key, bool isDownloads = false, bool isCache = false, - List<_i40.PageRouteInfo>? children, + List<_i39.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -430,7 +429,7 @@ class LocalLibraryRoute extends _i40.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -454,7 +453,7 @@ class LocalLibraryRouteArgs { final String location; - final _i41.Key? key; + final _i40.Key? key; final bool isDownloads; @@ -468,8 +467,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i14.LogsPage] -class LogsRoute extends _i40.PageRouteInfo { - const LogsRoute({List<_i40.PageRouteInfo>? children}) +class LogsRoute extends _i39.PageRouteInfo { + const LogsRoute({List<_i39.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -477,7 +476,7 @@ class LogsRoute extends _i40.PageRouteInfo { static const String name = 'LogsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i14.LogsPage(); @@ -487,8 +486,8 @@ class LogsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i15.LyricsPage] -class LyricsRoute extends _i40.PageRouteInfo { - const LyricsRoute({List<_i40.PageRouteInfo>? children}) +class LyricsRoute extends _i39.PageRouteInfo { + const LyricsRoute({List<_i39.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -496,7 +495,7 @@ class LyricsRoute extends _i40.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i15.LyricsPage(); @@ -506,11 +505,11 @@ class LyricsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i16.MiniLyricsPage] -class MiniLyricsRoute extends _i40.PageRouteInfo { +class MiniLyricsRoute extends _i39.PageRouteInfo { MiniLyricsRoute({ - _i43.Key? key, - required _i43.Size prevSize, - List<_i40.PageRouteInfo>? children, + _i42.Key? key, + required _i42.Size prevSize, + List<_i39.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -522,7 +521,7 @@ class MiniLyricsRoute extends _i40.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -540,9 +539,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i43.Key? key; + final _i42.Key? key; - final _i43.Size prevSize; + final _i42.Size prevSize; @override String toString() { @@ -552,8 +551,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i17.PlayerLyricsPage] -class PlayerLyricsRoute extends _i40.PageRouteInfo { - const PlayerLyricsRoute({List<_i40.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i39.PageRouteInfo { + const PlayerLyricsRoute({List<_i39.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -561,7 +560,7 @@ class PlayerLyricsRoute extends _i40.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i17.PlayerLyricsPage(); @@ -571,8 +570,8 @@ class PlayerLyricsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i18.PlayerQueuePage] -class PlayerQueueRoute extends _i40.PageRouteInfo { - const PlayerQueueRoute({List<_i40.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i39.PageRouteInfo { + const PlayerQueueRoute({List<_i39.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -580,7 +579,7 @@ class PlayerQueueRoute extends _i40.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i18.PlayerQueuePage(); @@ -590,8 +589,8 @@ class PlayerQueueRoute extends _i40.PageRouteInfo { /// generated route for /// [_i19.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i40.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i39.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i39.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -599,7 +598,7 @@ class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i19.PlayerTrackSourcesPage(); @@ -609,12 +608,12 @@ class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { /// generated route for /// [_i20.PlaylistPage] -class PlaylistRoute extends _i40.PageRouteInfo { +class PlaylistRoute extends _i39.PageRouteInfo { PlaylistRoute({ - _i41.Key? key, + _i40.Key? key, required String id, - required _i42.SpotubeSimplePlaylistObject playlist, - List<_i40.PageRouteInfo>? children, + required _i41.SpotubeSimplePlaylistObject playlist, + List<_i39.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -628,7 +627,7 @@ class PlaylistRoute extends _i40.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -648,11 +647,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i41.Key? key; + final _i40.Key? key; final String id; - final _i42.SpotubeSimplePlaylistObject playlist; + final _i41.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -662,8 +661,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i21.ProfilePage] -class ProfileRoute extends _i40.PageRouteInfo { - const ProfileRoute({List<_i40.PageRouteInfo>? children}) +class ProfileRoute extends _i39.PageRouteInfo { + const ProfileRoute({List<_i39.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -671,7 +670,7 @@ class ProfileRoute extends _i40.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i21.ProfilePage(); @@ -681,8 +680,8 @@ class ProfileRoute extends _i40.PageRouteInfo { /// generated route for /// [_i22.RootAppPage] -class RootAppRoute extends _i40.PageRouteInfo { - const RootAppRoute({List<_i40.PageRouteInfo>? children}) +class RootAppRoute extends _i39.PageRouteInfo { + const RootAppRoute({List<_i39.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -690,7 +689,7 @@ class RootAppRoute extends _i40.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i22.RootAppPage(); @@ -700,8 +699,8 @@ class RootAppRoute extends _i40.PageRouteInfo { /// generated route for /// [_i23.SearchPage] -class SearchRoute extends _i40.PageRouteInfo { - const SearchRoute({List<_i40.PageRouteInfo>? children}) +class SearchRoute extends _i39.PageRouteInfo { + const SearchRoute({List<_i39.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -709,7 +708,7 @@ class SearchRoute extends _i40.PageRouteInfo { static const String name = 'SearchRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i23.SearchPage(); @@ -719,8 +718,8 @@ class SearchRoute extends _i40.PageRouteInfo { /// generated route for /// [_i24.SettingsMetadataProviderPage] -class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { - const SettingsMetadataProviderRoute({List<_i40.PageRouteInfo>? children}) +class SettingsMetadataProviderRoute extends _i39.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i39.PageRouteInfo>? children}) : super( SettingsMetadataProviderRoute.name, initialChildren: children, @@ -728,7 +727,7 @@ class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { static const String name = 'SettingsMetadataProviderRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i24.SettingsMetadataProviderPage(); @@ -738,8 +737,8 @@ class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { /// generated route for /// [_i25.SettingsPage] -class SettingsRoute extends _i40.PageRouteInfo { - const SettingsRoute({List<_i40.PageRouteInfo>? children}) +class SettingsRoute extends _i39.PageRouteInfo { + const SettingsRoute({List<_i39.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -747,7 +746,7 @@ class SettingsRoute extends _i40.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i25.SettingsPage(); @@ -757,8 +756,8 @@ class SettingsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i26.StatsAlbumsPage] -class StatsAlbumsRoute extends _i40.PageRouteInfo { - const StatsAlbumsRoute({List<_i40.PageRouteInfo>? children}) +class StatsAlbumsRoute extends _i39.PageRouteInfo { + const StatsAlbumsRoute({List<_i39.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -766,7 +765,7 @@ class StatsAlbumsRoute extends _i40.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i26.StatsAlbumsPage(); @@ -776,8 +775,8 @@ class StatsAlbumsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i27.StatsArtistsPage] -class StatsArtistsRoute extends _i40.PageRouteInfo { - const StatsArtistsRoute({List<_i40.PageRouteInfo>? children}) +class StatsArtistsRoute extends _i39.PageRouteInfo { + const StatsArtistsRoute({List<_i39.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -785,7 +784,7 @@ class StatsArtistsRoute extends _i40.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i27.StatsArtistsPage(); @@ -795,8 +794,8 @@ class StatsArtistsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i28.StatsMinutesPage] -class StatsMinutesRoute extends _i40.PageRouteInfo { - const StatsMinutesRoute({List<_i40.PageRouteInfo>? children}) +class StatsMinutesRoute extends _i39.PageRouteInfo { + const StatsMinutesRoute({List<_i39.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -804,7 +803,7 @@ class StatsMinutesRoute extends _i40.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i28.StatsMinutesPage(); @@ -814,8 +813,8 @@ class StatsMinutesRoute extends _i40.PageRouteInfo { /// generated route for /// [_i29.StatsPage] -class StatsRoute extends _i40.PageRouteInfo { - const StatsRoute({List<_i40.PageRouteInfo>? children}) +class StatsRoute extends _i39.PageRouteInfo { + const StatsRoute({List<_i39.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -823,7 +822,7 @@ class StatsRoute extends _i40.PageRouteInfo { static const String name = 'StatsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i29.StatsPage(); @@ -833,8 +832,8 @@ class StatsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i30.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i40.PageRouteInfo { - const StatsPlaylistsRoute({List<_i40.PageRouteInfo>? children}) +class StatsPlaylistsRoute extends _i39.PageRouteInfo { + const StatsPlaylistsRoute({List<_i39.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -842,7 +841,7 @@ class StatsPlaylistsRoute extends _i40.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i30.StatsPlaylistsPage(); @@ -852,8 +851,8 @@ class StatsPlaylistsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i31.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i40.PageRouteInfo { - const StatsStreamFeesRoute({List<_i40.PageRouteInfo>? children}) +class StatsStreamFeesRoute extends _i39.PageRouteInfo { + const StatsStreamFeesRoute({List<_i39.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -861,7 +860,7 @@ class StatsStreamFeesRoute extends _i40.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i31.StatsStreamFeesPage(); @@ -871,8 +870,8 @@ class StatsStreamFeesRoute extends _i40.PageRouteInfo { /// generated route for /// [_i32.StatsStreamsPage] -class StatsStreamsRoute extends _i40.PageRouteInfo { - const StatsStreamsRoute({List<_i40.PageRouteInfo>? children}) +class StatsStreamsRoute extends _i39.PageRouteInfo { + const StatsStreamsRoute({List<_i39.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -880,7 +879,7 @@ class StatsStreamsRoute extends _i40.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i32.StatsStreamsPage(); @@ -890,11 +889,11 @@ class StatsStreamsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i33.TrackPage] -class TrackRoute extends _i40.PageRouteInfo { +class TrackRoute extends _i39.PageRouteInfo { TrackRoute({ - _i43.Key? key, + _i42.Key? key, required String trackId, - List<_i40.PageRouteInfo>? children, + List<_i39.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -907,7 +906,7 @@ class TrackRoute extends _i40.PageRouteInfo { static const String name = 'TrackRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -927,7 +926,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i43.Key? key; + final _i42.Key? key; final String trackId; @@ -939,8 +938,8 @@ class TrackRouteArgs { /// generated route for /// [_i34.UserAlbumsPage] -class UserAlbumsRoute extends _i40.PageRouteInfo { - const UserAlbumsRoute({List<_i40.PageRouteInfo>? children}) +class UserAlbumsRoute extends _i39.PageRouteInfo { + const UserAlbumsRoute({List<_i39.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -948,7 +947,7 @@ class UserAlbumsRoute extends _i40.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i34.UserAlbumsPage(); @@ -958,8 +957,8 @@ class UserAlbumsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i35.UserArtistsPage] -class UserArtistsRoute extends _i40.PageRouteInfo { - const UserArtistsRoute({List<_i40.PageRouteInfo>? children}) +class UserArtistsRoute extends _i39.PageRouteInfo { + const UserArtistsRoute({List<_i39.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -967,7 +966,7 @@ class UserArtistsRoute extends _i40.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i35.UserArtistsPage(); @@ -977,8 +976,8 @@ class UserArtistsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i36.UserDownloadsPage] -class UserDownloadsRoute extends _i40.PageRouteInfo { - const UserDownloadsRoute({List<_i40.PageRouteInfo>? children}) +class UserDownloadsRoute extends _i39.PageRouteInfo { + const UserDownloadsRoute({List<_i39.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -986,7 +985,7 @@ class UserDownloadsRoute extends _i40.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i36.UserDownloadsPage(); @@ -996,8 +995,8 @@ class UserDownloadsRoute extends _i40.PageRouteInfo { /// generated route for /// [_i37.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i40.PageRouteInfo { - const UserLocalLibraryRoute({List<_i40.PageRouteInfo>? children}) +class UserLocalLibraryRoute extends _i39.PageRouteInfo { + const UserLocalLibraryRoute({List<_i39.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1005,7 +1004,7 @@ class UserLocalLibraryRoute extends _i40.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i37.UserLocalLibraryPage(); @@ -1015,8 +1014,8 @@ class UserLocalLibraryRoute extends _i40.PageRouteInfo { /// generated route for /// [_i38.UserPlaylistsPage] -class UserPlaylistsRoute extends _i40.PageRouteInfo { - const UserPlaylistsRoute({List<_i40.PageRouteInfo>? children}) +class UserPlaylistsRoute extends _i39.PageRouteInfo { + const UserPlaylistsRoute({List<_i39.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1024,29 +1023,10 @@ class UserPlaylistsRoute extends _i40.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i40.PageInfo page = _i40.PageInfo( + static _i39.PageInfo page = _i39.PageInfo( name, builder: (data) { return const _i38.UserPlaylistsPage(); }, ); } - -/// generated route for -/// [_i39.WebViewLoginPage] -class WebViewLoginRoute extends _i40.PageRouteInfo { - const WebViewLoginRoute({List<_i40.PageRouteInfo>? children}) - : super( - WebViewLoginRoute.name, - initialChildren: children, - ); - - static const String name = 'WebViewLoginRoute'; - - static _i40.PageInfo page = _i40.PageInfo( - name, - builder: (data) { - return const _i39.WebViewLoginPage(); - }, - ); -} diff --git a/lib/components/fallbacks/anonymous_fallback.dart b/lib/components/fallbacks/anonymous_fallback.dart index 293df932..35e2d79a 100644 --- a/lib/components/fallbacks/anonymous_fallback.dart +++ b/lib/components/fallbacks/anonymous_fallback.dart @@ -5,8 +5,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/utils/platform.dart'; class AnonymousFallback extends ConsumerWidget { @@ -18,13 +18,13 @@ class AnonymousFallback extends ConsumerWidget { @override Widget build(BuildContext context, ref) { - final isLoggedIn = ref.watch(authenticationProvider); + final isLoggedIn = ref.watch(metadataPluginAuthenticatedProvider); if (isLoggedIn.isLoading) { return const Center(child: CircularProgressIndicator()); } - if (isLoggedIn.asData?.value != null && child != null) return child!; + if (isLoggedIn.asData?.value == true && child != null) return child!; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.center, diff --git a/lib/components/heart_button/heart_button.dart b/lib/components/heart_button/heart_button.dart index fa9508f0..af5bbd78 100644 --- a/lib/components/heart_button/heart_button.dart +++ b/lib/components/heart_button/heart_button.dart @@ -4,7 +4,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/heart_button/use_track_toggle_like.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; import 'package:spotube/provider/metadata_plugin/user.dart'; @@ -29,9 +29,9 @@ class HeartButton extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); - if (auth.asData?.value == null) return const SizedBox.shrink(); + if (authenticated.asData?.value != true) return const SizedBox.shrink(); return Tooltip( tooltip: TooltipContainer(child: Text(tooltip ?? "")).call, diff --git a/lib/components/track_presentation/presentation_top.dart b/lib/components/track_presentation/presentation_top.dart index ff7f5167..ae2be910 100644 --- a/lib/components/track_presentation/presentation_top.dart +++ b/lib/components/track_presentation/presentation_top.dart @@ -226,6 +226,7 @@ class TrackPresentationTopSection extends HookConsumerWidget { .imageProvider( options.ownerImage!, ), + size: 20 * scale, ) : null, child: Text( diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 22bdad78..d4182f00 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -22,11 +22,11 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; @@ -181,7 +181,7 @@ class TrackOptions extends HookConsumerWidget { final playlist = ref.watch(audioPlayerProvider); final playback = ref.watch(audioPlayerProvider.notifier); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); ref.watch(downloadManagerProvider); final downloadManager = ref.watch(downloadManagerProvider.notifier); final blacklist = ref.watch(blacklistProvider); @@ -430,7 +430,7 @@ class TrackOptions extends HookConsumerWidget { : context.l10n.save_as_favorite, ), ), - if (auth.asData?.value != null && !isLocalTrack) ...[ + if (authenticated.asData?.value == true && !isLocalTrack) ...[ AdaptiveMenuButton( value: TrackOptionValue.startRadio, leading: const Icon(SpotubeIcons.radio), @@ -442,7 +442,9 @@ class TrackOptions extends HookConsumerWidget { child: Text(context.l10n.add_to_playlist), ), ], - if (userPlaylist && auth.asData?.value != null && !isLocalTrack) + if (userPlaylist && + authenticated.asData?.value == true && + !isLocalTrack) AdaptiveMenuButton( value: TrackOptionValue.removeFromPlaylist, leading: const Icon(SpotubeIcons.removeFilled), diff --git a/lib/hooks/configurators/use_endless_playback.dart b/lib/hooks/configurators/use_endless_playback.dart index 81a66168..f010a0aa 100644 --- a/lib/hooks/configurators/use_endless_playback.dart +++ b/lib/hooks/configurators/use_endless_playback.dart @@ -2,7 +2,6 @@ import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; import 'package:spotube/services/logger/logger.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; diff --git a/lib/modules/home/sections/new_releases.dart b/lib/modules/home/sections/new_releases.dart index 31072954..e916ae4f 100644 --- a/lib/modules/home/sections/new_releases.dart +++ b/lib/modules/home/sections/new_releases.dart @@ -3,8 +3,8 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_playbutton_card_view.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/metadata_plugin/album/releases.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; class HomeNewReleasesSection extends HookConsumerWidget { @@ -12,13 +12,13 @@ class HomeNewReleasesSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final newReleases = ref.watch(metadataPluginAlbumReleasesProvider); final newReleasesNotifier = ref.read(metadataPluginAlbumReleasesProvider.notifier); - if (auth.asData?.value == null || + if (authenticated.asData?.value != true || newReleases.isLoading || newReleases.asData?.value.items.isEmpty == true) { return const SizedBox.shrink(); diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 8984455b..1dcd7de1 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -20,8 +20,8 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; @@ -41,7 +41,7 @@ class PlayerView extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { final theme = Theme.of(context); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final sourcedCurrentTrack = ref.watch(activeTrackSourcesProvider); final currentActiveTrack = ref.watch(audioPlayerProvider.select((s) => s.activeTrack)); @@ -242,8 +242,9 @@ class PlayerView extends HookConsumerWidget { }, ), ), - if (auth.asData?.value != null) const SizedBox(width: 10), - if (auth.asData?.value != null) + if (authenticated.asData?.value == true) + const SizedBox(width: 10), + if (authenticated.asData?.value == true) Expanded( child: OutlineButton( leading: const Icon(SpotubeIcons.music), diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index ee10e82a..e01b6926 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -16,8 +16,9 @@ import 'package:spotube/components/heart_button/heart_button.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/duration.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/sleep_timer_provider.dart'; class PlayerActions extends HookConsumerWidget { @@ -49,19 +50,19 @@ class PlayerActions extends HookConsumerWidget { downloader, ]); - final localTracks = [] /* ref.watch(localTracksProvider).value */; - final auth = ref.watch(authenticationProvider); + final localTracks = ref.watch(localTracksProvider).value; + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final sleepTimer = ref.watch(sleepTimerProvider); final sleepTimerNotifier = ref.watch(sleepTimerProvider.notifier); final isDownloaded = useMemoized(() { - return localTracks.any( - (element) => - element.name == playlist.activeTrack?.name && - element.album?.name == playlist.activeTrack?.album.name && - element.artists?.asString() == - playlist.activeTrack?.artists.asString(), - ) == + return localTracks?.values.expand((e) => e).any( + (element) => + element.name == playlist.activeTrack?.name && + element.album.name == playlist.activeTrack?.album.name && + element.artists.asString() == + playlist.activeTrack?.artists.asString(), + ) == true; }, [localTracks, playlist.activeTrack]); @@ -175,7 +176,7 @@ class PlayerActions extends HookConsumerWidget { ), if (playlist.activeTrack != null && !isLocalTrack && - auth.asData?.value != null) + authenticated.asData?.value == true) TrackHeartButton(track: playlist.activeTrack!), AdaptivePopSheetList( tooltip: context.l10n.sleep_timer, diff --git a/lib/modules/root/sidebar/sidebar_footer.dart b/lib/modules/root/sidebar/sidebar_footer.dart index f7168086..6ff3ab23 100644 --- a/lib/modules/root/sidebar/sidebar_footer.dart +++ b/lib/modules/root/sidebar/sidebar_footer.dart @@ -10,8 +10,8 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/connect/connect_device.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/download_manager_provider.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/user.dart'; class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { @@ -33,7 +33,7 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { placeholder: ImagePlaceholder.artist, ); - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); if (mediaQuery.mdAndDown) { return Column( @@ -91,7 +91,7 @@ class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { mainAxisSize: MainAxisSize.min, mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - if (auth.asData?.value != null && data == null) + if (authenticated.asData?.value == true && data == null) const CircularProgressIndicator() else if (data != null) Flexible( diff --git a/lib/pages/album/album.dart b/lib/pages/album/album.dart index c3a9568a..57b81cea 100644 --- a/lib/pages/album/album.dart +++ b/lib/pages/album/album.dart @@ -47,7 +47,7 @@ class AlbumPage extends HookConsumerWidget { title: album.name, description: "${context.l10n.released} • ${album.releaseDate} • ${album.artists.first.name}", - tracks: [], + tracks: tracks.asData?.value.items ?? [], pagination: PaginationProps( hasNextPage: tracks.asData?.value.hasMore ?? false, isLoading: tracks.isLoading || tracks.isLoadingNextPage, diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 3a850668..b11e8d66 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -10,9 +10,9 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; +import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:spotube/utils/primitive_utils.dart'; @@ -28,7 +28,7 @@ class ArtistPageHeader extends HookConsumerWidget { final theme = Theme.of(context); final ThemeData(:typography) = theme; - final auth = ref.watch(authenticationProvider); + final authenticated = ref.watch(metadataPluginAuthenticatedProvider); ref.watch(blacklistProvider); final blacklistNotifier = ref.watch(blacklistProvider.notifier); final isBlackListed = blacklistNotifier.containsArtist(artist.id); @@ -41,7 +41,7 @@ class ArtistPageHeader extends HookConsumerWidget { child: Row( mainAxisSize: MainAxisSize.min, children: [ - if (auth.asData?.value != null) + if (authenticated.asData?.value == true) Consumer( builder: (context, ref, _) { final isFollowingQuery = ref.watch( diff --git a/lib/pages/getting_started/sections/support.dart b/lib/pages/getting_started/sections/support.dart index 9559d28d..1b50f839 100644 --- a/lib/pages/getting_started/sections/support.dart +++ b/lib/pages/getting_started/sections/support.dart @@ -6,7 +6,6 @@ import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/pages/mobile_login/hooks/login_callback.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -15,8 +14,6 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final onLogin = useLoginCallback(ref); - return Center( child: Column( mainAxisSize: MainAxisSize.min, @@ -117,31 +114,6 @@ class GettingStartedScreenSupportSection extends HookConsumerWidget { }, child: Text(context.l10n.browse_anonymously), ), - const Gap(16), - Button.primary( - leading: const Icon(SpotubeIcons.spotify), - style: ButtonVariance.primary.copyWith( - decoration: (context, states, value) { - if (states.isNotEmpty) { - return ButtonVariance.primary - .decoration(context, states); - } - - return BoxDecoration( - color: const Color(0xff1db954), - borderRadius: BorderRadius.circular(8), - ); - }, - ), - onPressed: () async { - await KVStoreService.setDoneGettingStarted(true); - await onLogin(); - }, - child: Text( - context.l10n.connect_with_spotify, - style: const TextStyle(color: Colors.white), - ), - ), ], ), ), diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index c2cec373..3494211e 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -14,7 +14,6 @@ import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index 1963c7e8..16bd7882 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -40,7 +40,7 @@ class UserPlaylistsPage extends HookConsumerWidget { () => me.asData?.value == null ? null : SpotubeSimplePlaylistObject( - id: "liked-tracks", + id: "user-liked-tracks", name: context.l10n.liked_tracks, description: context.l10n.liked_tracks_description, externalUri: "", diff --git a/lib/pages/mobile_login/hooks/login_callback.dart b/lib/pages/mobile_login/hooks/login_callback.dart deleted file mode 100644 index 986b7f4a..00000000 --- a/lib/pages/mobile_login/hooks/login_callback.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:io'; - -import 'package:auto_route/auto_route.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide join; -import 'package:flutter/services.dart'; -import 'package:flutter_hooks/flutter_hooks.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:path/path.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/pages/mobile_login/no_webview_runtime_dialog.dart'; -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/utils/platform.dart'; - -Future Function() useLoginCallback(WidgetRef ref) { - final context = useContext(); - final theme = Theme.of(context); - final authNotifier = ref.read(authenticationProvider.notifier); - - return useCallback(() async { - if (kIsMobile || kIsMacOS) { - context.navigateTo(const WebViewLoginRoute()); - return; - } - - try { - final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); - final applicationSupportDir = await getApplicationSupportDirectory(); - final userDataFolder = Directory( - join(applicationSupportDir.path, "webview_window_Webview2"), - ); - - if (!await userDataFolder.exists()) { - await userDataFolder.create(); - } - - final webview = await WebviewWindow.create( - configuration: CreateConfiguration( - title: "Spotify Login", - titleBarTopPadding: kIsMacOS ? 20 : 0, - windowHeight: 720, - windowWidth: 1280, - userDataFolderWindows: userDataFolder.path, - ), - ); - webview - ..setBrightness(theme.colorScheme.brightness) - ..launch("https://accounts.spotify.com/") - ..setOnUrlRequestCallback((url) { - if (exp.hasMatch(url)) { - webview.getAllCookies().then((cookies) async { - final cookieHeader = - "sp_dc=${cookies.firstWhere((element) => element.name.contains("sp_dc")).value.replaceAll("\u0000", "")}"; - - await authNotifier.login(cookieHeader); - - webview.close(); - if (context.mounted) { - context.navigateTo(const HomeRoute()); - } - }); - } - - return true; - }); - } on PlatformException catch (_) { - if (!await WebviewWindow.isWebviewAvailable()) { - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - showDialog( - context: context, - builder: (context) { - return const NoWebviewRuntimeDialog(); - }, - ); - }); - } - } - }, [authNotifier, theme, context.navigateTo]); -} diff --git a/lib/pages/mobile_login/mobile_login.dart b/lib/pages/mobile_login/mobile_login.dart deleted file mode 100644 index eb50316f..00000000 --- a/lib/pages/mobile_login/mobile_login.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart'; - -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/components/button/back_button.dart'; -import 'package:spotube/components/titlebar/titlebar.dart'; - -import 'package:spotube/provider/authentication/authentication.dart'; -import 'package:spotube/utils/platform.dart'; -import 'package:auto_route/auto_route.dart'; - -@RoutePage() -class WebViewLoginPage extends HookConsumerWidget { - static const name = "login"; - const WebViewLoginPage({super.key}); - - @override - Widget build(BuildContext context, ref) { - final authenticationNotifier = ref.watch(authenticationProvider.notifier); - - if (kIsDesktop) { - const Scaffold( - child: Center( - child: Text('This feature is not available on desktop'), - ), - ); - } - - return SafeArea( - bottom: false, - child: Scaffold( - headers: const [ - TitleBar( - leading: [BackButton(color: Colors.white)], - backgroundColor: Colors.transparent, - ), - ], - floatingHeader: true, - child: InAppWebView( - initialSettings: InAppWebViewSettings( - userAgent: - "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 safari/537.36", - ), - initialUrlRequest: URLRequest( - url: WebUri("https://accounts.spotify.com/"), - ), - onPermissionRequest: (controller, permissionRequest) async { - return PermissionResponse( - resources: permissionRequest.resources, - action: PermissionResponseAction.GRANT, - ); - }, - onLoadStop: (controller, action) async { - if (action == null) return; - String url = action.toString(); - if (url.endsWith("/")) { - url = url.substring(0, url.length - 1); - } - - final exp = RegExp(r"https:\/\/accounts.spotify.com\/.+\/status"); - - if (exp.hasMatch(url)) { - final cookies = - await CookieManager.instance().getCookies(url: action); - final cookieHeader = - "sp_dc=${cookies.firstWhere((element) => element.name == "sp_dc").value}"; - - await authenticationNotifier.login(cookieHeader); - if (context.mounted) { - // ignore: use_build_context_synchronously - context.navigateTo(const HomeRoute()); - } - } - }, - ), - ), - ); - } -} diff --git a/lib/pages/mobile_login/no_webview_runtime_dialog.dart b/lib/pages/mobile_login/no_webview_runtime_dialog.dart deleted file mode 100644 index b0919e5c..00000000 --- a/lib/pages/mobile_login/no_webview_runtime_dialog.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:shadcn_flutter/shadcn_flutter.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:url_launcher/url_launcher_string.dart'; - -class NoWebviewRuntimeDialog extends StatelessWidget { - const NoWebviewRuntimeDialog({super.key}); - - @override - Widget build(BuildContext context) { - final ThemeData(:platform) = Theme.of(context); - - return AlertDialog( - title: Text(context.l10n.webview_not_found), - content: Text(context.l10n.webview_not_found_description), - actions: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text(context.l10n.cancel), - ), - Button.primary( - onPressed: () async { - final url = switch (platform) { - TargetPlatform.windows => - 'https://developer.microsoft.com/en-us/microsoft-edge/webview2', - TargetPlatform.macOS => 'https://www.apple.com/safari/', - TargetPlatform.linux => - 'https://webkitgtk.org/reference/webkit2gtk/stable/', - _ => "", - }; - if (url.isEmpty) { - showToast( - context: context, - builder: (context, overlay) { - return const SurfaceCard( - child: Basic( - title: Text('Unsupported platform'), - ), - ); - }, - ); - } - - await launchUrlString(url); - }, - child: Text(switch (platform) { - TargetPlatform.windows => 'Download Edge WebView2', - TargetPlatform.macOS => 'Download Safari', - TargetPlatform.linux => 'Download Webkit2Gtk', - _ => 'Download Webview', - }), - ), - ], - ); - } -} diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index b481300b..955ff59b 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -17,6 +17,7 @@ import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dar import 'package:spotube/pages/search/sections/albums.dart'; import 'package:spotube/pages/search/sections/artists.dart'; import 'package:spotube/pages/search/sections/playlists.dart'; +import 'package:spotube/pages/search/sections/tracks.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; @@ -227,7 +228,7 @@ class SearchPage extends HookConsumerWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - // SearchTracksSection(), + SearchTracksSection(), SearchPlaylistsSection(), Gap(20), SearchArtistsSection(), diff --git a/lib/provider/authentication/authentication.dart b/lib/provider/authentication/authentication.dart deleted file mode 100644 index 497ccf79..00000000 --- a/lib/provider/authentication/authentication.dart +++ /dev/null @@ -1,309 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; - -import 'package:collection/collection.dart'; -import 'package:desktop_webview_window/desktop_webview_window.dart'; -import 'package:dio/dio.dart'; -import 'package:dio_http2_adapter/dio_http2_adapter.dart'; -import 'package:drift/drift.dart'; -import 'package:flutter_inappwebview/flutter_inappwebview.dart' - hide X509Certificate; -import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:spotube/collections/routes.dart'; -import 'package:spotube/components/dialogs/prompt_dialog.dart'; -import 'package:spotube/extensions/context.dart'; -import 'package:spotube/models/database/database.dart'; -import 'package:spotube/provider/database/database.dart'; -import 'package:spotube/services/logger/logger.dart'; -import 'package:spotube/utils/platform.dart'; -import 'package:otp_util/otp_util.dart'; -// ignore: implementation_imports -import 'package:otp_util/src/utils/generic_util.dart'; -import 'package:spotube/utils/service_utils.dart'; - -extension ExpirationAuthenticationTableData on AuthenticationTableData { - bool get isExpired => DateTime.now().isAfter(expiration); - - String? getCookie(String key) => cookie.value - .split("; ") - .firstWhereOrNull((c) => c.trim().startsWith("$key=")) - ?.trim() - .split("=") - .last - .replaceAll(";", ""); -} - -class AuthenticationNotifier extends AsyncNotifier { - static final Dio dio = () { - final dio = Dio() - ..httpClientAdapter = Http2Adapter( - ConnectionManager( - idleTimeout: const Duration(seconds: 10), - onClientCreate: (uri, clientSettings) { - clientSettings.onBadCertificate = (X509Certificate cert) { - return uri.host.endsWith("spotify.com"); - }; - }, - ), - ); - - return dio; - }(); - - @override - build() async { - final database = ref.watch(databaseProvider); - - final data = await (database.select(database.authenticationTable) - ..where((s) => s.id.equals(0))) - .getSingleOrNull(); - - Timer? refreshTimer; - - listenSelf((prevData, newData) async { - if (newData.asData?.value == null) return; - - if (newData.asData!.value!.isExpired) { - await refreshCredentials(); - } - - // set the refresh timer - refreshTimer?.cancel(); - refreshTimer = Timer( - newData.asData!.value!.expiration.difference(DateTime.now()), - () => refreshCredentials(), - ); - }); - - final subscription = - database.select(database.authenticationTable).watch().listen( - (event) { - state = AsyncData(event.isEmpty ? null : event.first); - }, - ); - - ref.onDispose(() { - subscription.cancel(); - refreshTimer?.cancel(); - }); - - return data; - } - - Future refreshCredentials() async { - final database = ref.read(databaseProvider); - final refreshedCredentials = - await credentialsFromCookie(state.asData!.value!.cookie.value); - - await database - .update(database.authenticationTable) - .replace(refreshedCredentials); - } - - Future login(String cookie) async { - final database = ref.read(databaseProvider); - final refreshedCredentials = await credentialsFromCookie(cookie); - - await database - .into(database.authenticationTable) - .insert(refreshedCredentials, mode: InsertMode.replace); - } - - String base32FromBytes(Uint8List e, String secretSauce) { - var t = 0; - var n = 0; - var r = ""; - for (int i = 0; i < e.length; i++) { - n = n << 8 | e[i]; - t += 8; - while (t >= 5) { - r += secretSauce[n >>> t - 5 & 31]; - t -= 5; - } - } - if (t > 0) { - r += secretSauce[n << 5 - t & 31]; - } - return r; - } - - Uint8List cleanBuffer(String e) { - e = e.replaceAll(" ", ""); - final t = List.filled(e.length ~/ 2, 0); - final n = Uint8List.fromList(t); - for (int r = 0; r < e.length; r += 2) { - n[r ~/ 2] = int.parse(e.substring(r, r + 2), radix: 16); - } - return n; - } - - Future generateTotp() async { - const secretSauce = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567"; - final secretCipherBytes = const [ - 12, - 56, - 76, - 33, - 88, - 44, - 88, - 33, - 78, - 78, - 11, - 66, - 22, - 22, - 55, - 69, - 54 - ].mapIndexed((t, e) => e ^ t % 33 + 9).toList(); - - final secretBytes = cleanBuffer( - utf8 - .encode(secretCipherBytes.join("")) - .map((e) => e.toRadixString(16)) - .join(), - ); - - final secret = base32FromBytes(secretBytes, secretSauce); - - final res = await dio.get( - "https://open.spotify.com/server-time", - options: Options( - headers: { - "Host": "open.spotify.com", - "User-Agent": ServiceUtils.randomUserAgent( - kIsDesktop ? UserAgentDevice.desktop : UserAgentDevice.mobile, - ), - "accept": "*/*", - }, - ), - ); - final serverTimeSeconds = res.data["serverTime"] as int; - - final totp = TOTP( - secret: secret, - algorithm: OTPAlgorithm.SHA1, - digits: 6, - interval: 30, - ); - - return totp.generateOTP( - input: Util.timeFormat( - time: DateTime.fromMillisecondsSinceEpoch(serverTimeSeconds * 1000), - interval: 30, - ), - ); - } - - Future getToken({ - required String totp, - required int timestamp, - String mode = "transport", - String? spDc, - }) async { - assert(mode == "transport" || mode == "init"); - - final accessTokenUrl = Uri.parse( - "https://open.spotify.com/get_access_token?reason=$mode&productType=web-player" - "&totp=$totp&totpVer=5&ts=$timestamp", - ); - - final res = await dio.getUri( - accessTokenUrl, - options: Options( - headers: { - "Cookie": spDc ?? "", - "User-Agent": ServiceUtils.randomUserAgent( - kIsDesktop ? UserAgentDevice.desktop : UserAgentDevice.mobile, - ), - }, - ), - ); - - return res; - } - - Future credentialsFromCookie( - String cookie, - ) async { - try { - final spDc = cookie - .split("; ") - .firstWhereOrNull((c) => c.trim().startsWith("sp_dc=")) - ?.trim(); - - final totp = await generateTotp(); - - final timestamp = (DateTime.now().millisecondsSinceEpoch / 1000).floor(); - - var res = await getToken( - totp: totp, - timestamp: timestamp, - spDc: spDc, - mode: "transport", - ); - - if ((res.data["accessToken"]?.length ?? 0) != 374) { - res = await getToken( - totp: totp, - timestamp: timestamp, - spDc: spDc, - mode: "init", - ); - } - - final body = res.data as Map; - - if (body["accessToken"] == null) { - AppLogger.reportError( - "The access token is only ${body["accessToken"]?.length} characters long instead of 374\n" - "Your authentication probably doesn't work", - StackTrace.current, - ); - } - - return AuthenticationTableCompanion.insert( - id: const Value(0), - cookie: DecryptedText("${res.headers["set-cookie"]?.join(";")}; $spDc"), - accessToken: DecryptedText(body['accessToken']), - expiration: DateTime.fromMillisecondsSinceEpoch( - body['accessTokenExpirationTimestampMs'], - ), - ); - } catch (e) { - if (rootNavigatorKey.currentContext != null) { - showPromptDialog( - context: rootNavigatorKey.currentContext!, - title: rootNavigatorKey.currentContext!.l10n - .error("Authentication Failure"), - message: e.toString(), - cancelText: null, - ); - } - rethrow; - } - } - - Future logout() async { - state = const AsyncData(null); - final database = ref.read(databaseProvider); - await (database.delete(database.authenticationTable) - ..where((s) => s.id.equals(0))) - .go(); - if (kIsMobile) { - WebStorageManager.instance().deleteAllData(); - CookieManager.instance().deleteAllCookies(); - } - if (kIsDesktop) { - await WebviewWindow.clearAll(); - } - } -} - -final authenticationProvider = - AsyncNotifierProvider( - () => AuthenticationNotifier(), -); diff --git a/lib/provider/metadata_plugin/library/tracks.dart b/lib/provider/metadata_plugin/library/tracks.dart index 9645deda..6ef7a030 100644 --- a/lib/provider/metadata_plugin/library/tracks.dart +++ b/lib/provider/metadata_plugin/library/tracks.dart @@ -1,7 +1,6 @@ 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'; @@ -11,17 +10,7 @@ class MetadataPluginSavedTracksNotifier @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, + final tracks = await (await metadataPlugin).user.savedTracks( offset: offset, limit: limit, ); From 53ad5bd448f656aa357c01b0d91ab98f8f02c6d4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 19 Jun 2025 23:04:24 +0600 Subject: [PATCH 28/60] feat: remove green corp names formally --- .env.example | 7 +- .github/workflows/pr-lint.yml | 1 - android/app/src/main/AndroidManifest.xml | 6 - lib/collections/env.dart | 16 - .../{spotify_markets.dart => markets.dart} | 2 +- lib/collections/spotube_icons.dart | 1 - .../fallbacks/anonymous_fallback.dart | 2 +- lib/components/track_tile/track_options.dart | 129 ++- lib/l10n/app_en.arb | 847 +++++++++--------- lib/l10n/generated/app_localizations.dart | 86 +- lib/l10n/generated/app_localizations_ar.dart | 43 +- lib/l10n/generated/app_localizations_bn.dart | 43 +- lib/l10n/generated/app_localizations_ca.dart | 43 +- lib/l10n/generated/app_localizations_cs.dart | 43 +- lib/l10n/generated/app_localizations_de.dart | 43 +- lib/l10n/generated/app_localizations_en.dart | 45 +- lib/l10n/generated/app_localizations_es.dart | 43 +- lib/l10n/generated/app_localizations_eu.dart | 43 +- lib/l10n/generated/app_localizations_fa.dart | 43 +- lib/l10n/generated/app_localizations_fi.dart | 43 +- lib/l10n/generated/app_localizations_fr.dart | 43 +- lib/l10n/generated/app_localizations_hi.dart | 43 +- lib/l10n/generated/app_localizations_id.dart | 43 +- lib/l10n/generated/app_localizations_it.dart | 43 +- lib/l10n/generated/app_localizations_ja.dart | 43 +- lib/l10n/generated/app_localizations_ka.dart | 43 +- lib/l10n/generated/app_localizations_ko.dart | 43 +- lib/l10n/generated/app_localizations_ne.dart | 43 +- lib/l10n/generated/app_localizations_nl.dart | 43 +- lib/l10n/generated/app_localizations_pl.dart | 43 +- lib/l10n/generated/app_localizations_pt.dart | 43 +- lib/l10n/generated/app_localizations_ru.dart | 43 +- lib/l10n/generated/app_localizations_ta.dart | 43 +- lib/l10n/generated/app_localizations_th.dart | 43 +- lib/l10n/generated/app_localizations_tl.dart | 43 +- lib/l10n/generated/app_localizations_tr.dart | 43 +- lib/l10n/generated/app_localizations_uk.dart | 43 +- lib/l10n/generated/app_localizations_vi.dart | 43 +- lib/l10n/generated/app_localizations_zh.dart | 43 +- lib/main.dart | 3 +- .../database/tables/audio_player_state.dart | 16 +- lib/models/metadata/image.dart | 2 +- lib/models/metadata/metadata.dart | 1 + lib/models/metadata/track.dart | 6 +- .../playlist/playlist_create_dialog.dart | 18 - .../getting_started/sections/region.dart | 14 +- lib/pages/profile/profile.dart | 4 +- .../settings/sections/language_region.dart | 4 +- lib/pages/stats/fees/fees.dart | 2 +- lib/provider/audio_player/audio_player.dart | 4 + lib/provider/discord_provider.dart | 6 +- .../local_tracks/local_tracks_provider.dart | 5 + .../metadata_plugin/library/tracks.dart | 7 +- lib/provider/server/routes/playback.dart | 3 +- lib/utils/service_utils.dart | 1 - linux/packaging/appimage/make_config.yaml | 5 +- linux/packaging/deb/make_config.yaml | 5 +- linux/packaging/rpm/make_config.yaml | 5 +- linux/spotube.desktop | 2 +- macos/Runner/Info.plist | 24 +- pubspec.yaml | 2 +- untranslated_messages.json | 109 +++ 62 files changed, 690 insertions(+), 1904 deletions(-) rename lib/collections/{spotify_markets.dart => markets.dart} (99%) diff --git a/.env.example b/.env.example index 35c5d563..bbab9bb8 100644 --- a/.env.example +++ b/.env.example @@ -1,7 +1,3 @@ -# The format: -# SPOTIFY_SECRETS=clintId1:clientSecret1,clientId2:clientSecret2 -SPOTIFY_SECRETS=$SPOTIFY_SECRETS - # 0 or 1 # 0 = disable # 1 = enable @@ -13,5 +9,4 @@ LASTFM_API_SECRET=$LASTFM_API_SECRET # Release channel. Can be: nightly, stable RELEASE_CHANNEL=$RELEASE_CHANNEL -HIDE_DONATIONS=$HIDE_DONATIONS -DISABLE_SPOTIFY_IMAGES=$DISABLE_SPOTIFY_IMAGES +HIDE_DONATIONS=$HIDE_DONATIONS \ No newline at end of file diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 245101d8..31df69a7 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -21,7 +21,6 @@ jobs: run: | envsubst < .env.example > .env env: - SPOTIFY_SECRETS: xxx:xxx ENABLE_UPDATE_CHECK: true LASTFM_API_KEY: xxx LASTFM_API_SECRET: xxx diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0effefe2..a005257e 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -57,10 +57,6 @@ - - @@ -74,8 +70,6 @@ - - diff --git a/lib/collections/env.dart b/lib/collections/env.dart index feb2a2db..52ef2bbf 100644 --- a/lib/collections/env.dart +++ b/lib/collections/env.dart @@ -10,9 +10,6 @@ enum ReleaseChannel { @Envied(obfuscate: true, requireEnvFile: true, path: ".env") abstract class Env { - @EnviedField(varName: 'SPOTIFY_SECRETS') - static final String rawSpotifySecrets = _Env.rawSpotifySecrets; - @EnviedField(varName: 'LASTFM_API_KEY') static final String lastFmApiKey = _Env.lastFmApiKey; @@ -24,25 +21,12 @@ abstract class Env { static bool get hideDonations => _hideDonations == 1; - static final spotifySecrets = rawSpotifySecrets.split(',').map((e) { - final secrets = e.trim().split(":").map((e) => e.trim()); - return { - "clientId": secrets.first, - "clientSecret": secrets.last, - }; - }).toList(); - @EnviedField(varName: 'ENABLE_UPDATE_CHECK', defaultValue: "1") static final String _enableUpdateChecker = _Env._enableUpdateChecker; @EnviedField(varName: "RELEASE_CHANNEL", defaultValue: "nightly") static final String _releaseChannel = _Env._releaseChannel; - @EnviedField(varName: "DISABLE_SPOTIFY_IMAGES", defaultValue: "0") - static final String _disableSpotifyImages = _Env._disableSpotifyImages; - - static bool get disableSpotifyImages => _disableSpotifyImages == "1"; - static ReleaseChannel get releaseChannel => _releaseChannel == "stable" ? ReleaseChannel.stable : ReleaseChannel.nightly; diff --git a/lib/collections/spotify_markets.dart b/lib/collections/markets.dart similarity index 99% rename from lib/collections/spotify_markets.dart rename to lib/collections/markets.dart index c4788022..8398c662 100644 --- a/lib/collections/spotify_markets.dart +++ b/lib/collections/markets.dart @@ -2,7 +2,7 @@ import 'package:spotube/models/metadata/market.dart'; -final spotifyMarkets = [ +final marketsMap = [ (Market.AL, "Albania (AL)"), (Market.DZ, "Algeria (DZ)"), (Market.AD, "Andorra (AD)"), diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index 5efba917..9dd0bec2 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -105,7 +105,6 @@ abstract class SpotubeIcons { static const file = FeatherIcons.file; static const stream = Icons.stream_rounded; static const lastFm = SimpleIcons.lastdotfm; - static const spotify = SimpleIcons.spotify; static const eye = FeatherIcons.eye; static const noEye = FeatherIcons.eyeOff; static const normalize = FeatherIcons.barChart2; diff --git a/lib/components/fallbacks/anonymous_fallback.dart b/lib/components/fallbacks/anonymous_fallback.dart index 35e2d79a..ed769959 100644 --- a/lib/components/fallbacks/anonymous_fallback.dart +++ b/lib/components/fallbacks/anonymous_fallback.dart @@ -39,7 +39,7 @@ class AnonymousFallback extends ConsumerWidget { ), Text(context.l10n.not_logged_in), Button.primary( - child: Text(context.l10n.login_with_spotify), + child: Text(context.l10n.login), onPressed: () => context.navigateTo(const SettingsRoute()), ) ], diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index d4182f00..03c0244d 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -72,8 +72,7 @@ class TrackOptions extends HookConsumerWidget { ); void actionShare(BuildContext context, SpotubeTrackObject track) { - final data = "https://open.spotify.com/track/${track.id}"; - Clipboard.setData(ClipboardData(text: data)).then((_) { + Clipboard.setData(ClipboardData(text: track.externalUri)).then((_) { if (context.mounted) { showToast( context: context, @@ -81,7 +80,7 @@ class TrackOptions extends HookConsumerWidget { builder: (context, overlay) { return SurfaceCard( child: Text( - context.l10n.copied_to_clipboard(data), + context.l10n.copied_to_clipboard(track.externalUri), textAlign: TextAlign.center, ), ); @@ -108,71 +107,71 @@ class TrackOptions extends HookConsumerWidget { ); } - void actionStartRadio( - BuildContext context, - WidgetRef ref, - SpotubeTrackObject track, - ) async { - final playback = ref.read(audioPlayerProvider.notifier); - final playlist = ref.read(audioPlayerProvider); - final query = "${track.name} Radio"; - final metadataPlugin = await ref.read(metadataPluginProvider.future); + // void actionStartRadio( + // BuildContext context, + // WidgetRef ref, + // SpotubeTrackObject track, + // ) async { + // final playback = ref.read(audioPlayerProvider.notifier); + // final playlist = ref.read(audioPlayerProvider); + // final query = "${track.name} Radio"; + // final metadataPlugin = await ref.read(metadataPluginProvider.future); - if (metadataPlugin == null) { - throw MetadataPluginException.noDefaultPlugin( - "No default metadata plugin set", - ); - } + // if (metadataPlugin == null) { + // throw MetadataPluginException.noDefaultPlugin( + // "No default metadata plugin set", + // ); + // } - final pages = await metadataPlugin.search.playlists(query); + // final pages = await metadataPlugin.search.playlists(query); - final artists = track.artists.map((e) => e.name); + // final artists = track.artists.map((e) => e.name); - final radio = pages.items.firstWhere( - (e) { - final validPlaylists = artists.where((a) => e.description.contains(a)); - return e.name == "${track.name} Radio" && - (validPlaylists.length >= 2 || - validPlaylists.length == artists.length) && - e.owner.name == "Spotify"; - }, - orElse: () => pages.items.first, - ); + // final radio = pages.items.firstWhere( + // (e) { + // final validPlaylists = artists.where((a) => e.description.contains(a)); + // return e.name.contains(track.name) && + // e.name.contains("Radio") && + // (validPlaylists.length >= 2 || + // validPlaylists.length == artists.length); + // }, + // orElse: () => pages.items.first, + // ); - bool replaceQueue = false; + // bool replaceQueue = false; - if (context.mounted && playlist.tracks.isNotEmpty) { - replaceQueue = await showPromptDialog( - context: context, - title: context.l10n.how_to_start_radio, - message: context.l10n.replace_queue_question, - okText: context.l10n.replace, - cancelText: context.l10n.add_to_queue, - ); - } + // if (context.mounted && playlist.tracks.isNotEmpty) { + // replaceQueue = await showPromptDialog( + // context: context, + // title: context.l10n.how_to_start_radio, + // message: context.l10n.replace_queue_question, + // okText: context.l10n.replace, + // cancelText: context.l10n.add_to_queue, + // ); + // } - if (replaceQueue || playlist.tracks.isEmpty) { - await playback.stop(); - await playback.load([track], autoPlay: true); + // if (replaceQueue || playlist.tracks.isEmpty) { + // await playback.stop(); + // await playback.load([track], autoPlay: true); - // we don't have to add those tracks as useEndlessPlayback will do it for us - return; - } else { - await playback.addTrack(track); - } - await ref.read(metadataPluginPlaylistTracksProvider(radio.id).future); - final tracks = await ref - .read(metadataPluginPlaylistTracksProvider(radio.id).notifier) - .fetchAll(); + // // we don't have to add those tracks as useEndlessPlayback will do it for us + // return; + // } else { + // await playback.addTrack(track); + // } + // await ref.read(metadataPluginPlaylistTracksProvider(radio.id).future); + // final tracks = await ref + // .read(metadataPluginPlaylistTracksProvider(radio.id).notifier) + // .fetchAll(); - await playback.addTracks( - tracks.toList() - ..removeWhere((e) { - final isDuplicate = playlist.tracks.any((t) => t.id == e.id); - return e.id == track.id || isDuplicate; - }), - ); - } + // await playback.addTracks( + // tracks.toList() + // ..removeWhere((e) { + // final isDuplicate = playlist.tracks.any((t) => t.id == e.id); + // return e.id == track.id || isDuplicate; + // }), + // ); + // } @override Widget build(BuildContext context, ref) { @@ -339,7 +338,7 @@ class TrackOptions extends HookConsumerWidget { await downloadManager.addToQueue(track as SpotubeFullTrackObject); break; case TrackOptionValue.startRadio: - actionStartRadio(context, ref, track); + // actionStartRadio(context, ref, track); break; } }, @@ -431,11 +430,11 @@ class TrackOptions extends HookConsumerWidget { ), ), if (authenticated.asData?.value == true && !isLocalTrack) ...[ - AdaptiveMenuButton( - value: TrackOptionValue.startRadio, - leading: const Icon(SpotubeIcons.radio), - child: Text(context.l10n.start_a_radio), - ), + // AdaptiveMenuButton( + // value: TrackOptionValue.startRadio, + // leading: const Icon(SpotubeIcons.radio), + // child: Text(context.l10n.start_a_radio), + // ), AdaptiveMenuButton( value: TrackOptionValue.addToPlaylist, leading: const Icon(SpotubeIcons.playlistAdd), diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 4da423a8..cb81d3ce 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,432 +1,419 @@ { - "guest": "Guest", - "browse": "Browse", - "search": "Search", - "library": "Library", - "lyrics": "Lyrics", - "settings": "Settings", - "genre_categories_filter": "Filter categories or genres...", - "genre": "Genre", - "personalized": "Personalized", - "featured": "Featured", - "new_releases": "New Releases", - "songs": "Songs", - "playing_track": "Playing {track}", - "queue_clear_alert": "This will clear the current queue. {track_length} tracks will be removed\nDo you want to continue?", - "load_more": "Load more", - "playlists": "Playlists", - "artists": "Artists", - "albums": "Albums", - "tracks": "Tracks", - "downloads": "Downloads", - "filter_playlists": "Filter your playlists...", - "liked_tracks": "Liked Tracks", - "liked_tracks_description": "All your liked tracks", - "playlist": "Playlist", - "create_a_playlist": "Create a playlist", - "update_playlist": "Update playlist", - "create": "Create", - "cancel": "Cancel", - "update": "Update", - "playlist_name": "Playlist Name", - "name_of_playlist": "Name of the playlist", - "description": "Description", - "public": "Public", - "collaborative": "Collaborative", - "search_local_tracks": "Search local tracks...", - "play": "Play", - "delete": "Delete", - "none": "None", - "sort_a_z": "Sort by A-Z", - "sort_z_a": "Sort by Z-A", - "sort_artist": "Sort by Artist", - "sort_album": "Sort by Album", - "sort_duration": "Sort by Duration", - "sort_tracks": "Sort Tracks", - "currently_downloading": "Currently Downloading ({tracks_length})", - "cancel_all": "Cancel All", - "filter_artist": "Filter artists...", - "followers": "{followers} Followers", - "add_artist_to_blacklist": "Add artist to blacklist", - "top_tracks": "Top Tracks", - "fans_also_like": "Fans also like", - "loading": "Loading...", - "artist": "Artist", - "blacklisted": "Blacklisted", - "following": "Following", - "follow": "Follow", - "artist_url_copied": "Artist URL copied to clipboard", - "added_to_queue": "Added {tracks} tracks to queue", - "filter_albums": "Filter albums...", - "synced": "Synced", - "plain": "Plain", - "shuffle": "Shuffle", - "search_tracks": "Search tracks...", - "released": "Released", - "error": "Error {error}", - "title": "Title", - "time": "Time", - "more_actions": "More actions", - "download_count": "Download ({count})", - "add_count_to_playlist": "Add ({count}) to Playlist", - "add_count_to_queue": "Add ({count}) to Queue", - "play_count_next": "Play ({count}) next", - "album": "Album", - "copied_to_clipboard": "Copied {data} to clipboard", - "add_to_following_playlists": "Add {track} to following Playlists", - "add": "Add", - "added_track_to_queue": "Added {track} to queue", - "add_to_queue": "Add to queue", - "track_will_play_next": "{track} will play next", - "play_next": "Play next", - "removed_track_from_queue": "Removed {track} from queue", - "remove_from_queue": "Remove from queue", - "remove_from_favorites": "Remove from favorites", - "save_as_favorite": "Save as favorite", - "add_to_playlist": "Add to playlist", - "remove_from_playlist": "Remove from playlist", - "add_to_blacklist": "Add to blacklist", - "remove_from_blacklist": "Remove from blacklist", - "share": "Share", - "mini_player": "Mini Player", - "slide_to_seek": "Slide to seek forward or backward", - "shuffle_playlist": "Shuffle playlist", - "unshuffle_playlist": "Unshuffle playlist", - "previous_track": "Previous track", - "next_track": "Next track", - "pause_playback": "Pause Playback", - "resume_playback": "Resume Playback", - "loop_track": "Loop track", - "no_loop": "No loop", - "repeat_playlist": "Repeat playlist", - "queue": "Queue", - "alternative_track_sources": "Alternative track sources", - "download_track": "Download track", - "tracks_in_queue": "{tracks} tracks in queue", - "clear_all": "Clear all", - "show_hide_ui_on_hover": "Show/Hide UI on hover", - "always_on_top": "Always on top", - "exit_mini_player": "Exit Mini player", - "download_location": "Download location", - "local_library": "Local library", - "add_library_location": "Add to library", - "remove_library_location": "Remove from library", - "account": "Account", - "login_with_spotify": "Login with your Spotify account", - "connect_with_spotify": "Connect with Spotify", - "logout": "Logout", - "logout_of_this_account": "Logout of this account", - "language_region": "Language & Region", - "language": "Language", - "system_default": "System Default", - "market_place_region": "Marketplace Region", - "recommendation_country": "Recommendation Country", - "appearance": "Appearance", - "layout_mode": "Layout Mode", - "override_layout_settings": "Override responsive layout mode settings", - "adaptive": "Adaptive", - "compact": "Compact", - "extended": "Extended", - "theme": "Theme", - "dark": "Dark", - "light": "Light", - "system": "System", - "accent_color": "Accent Color", - "sync_album_color": "Sync album color", - "sync_album_color_description": "Uses the dominant color of the album art as the accent color", - "playback": "Playback", - "audio_quality": "Audio Quality", - "high": "High", - "low": "Low", - "pre_download_play": "Pre-download and play", - "pre_download_play_description": "Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)", - "skip_non_music": "Skip non-music segments (SponsorBlock)", - "blacklist_description": "Blacklisted tracks and artists", - "wait_for_download_to_finish": "Please wait for the current download to finish", - "desktop": "Desktop", - "close_behavior": "Close Behavior", - "close": "Close", - "minimize_to_tray": "Minimize to tray", - "show_tray_icon": "Show System tray icon", - "about": "About", - "u_love_spotube": "We know you love Spotube", - "check_for_updates": "Check for updates", - "about_spotube": "About Spotube", - "blacklist": "Blacklist", - "please_sponsor": "Please Sponsor/Donate", - "spotube_description": "Spotube, a lightweight, cross-platform, free-for-all spotify client", - "version": "Version", - "build_number": "Build Number", - "founder": "Founder", - "repository": "Repository", - "bug_issues": "Bug+Issues", - "made_with": "Made with ❤️ in Bangladesh🇧🇩", - "kingkor_roy_tirtho": "Kingkor Roy Tirtho", - "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", - "license": "License", - "add_spotify_credentials": "Add your spotify credentials to get started", - "credentials_will_not_be_shared_disclaimer": "Don't worry, any of your credentials won't be collected or shared with anyone", - "know_how_to_login": "Don't know how to do this?", - "follow_step_by_step_guide": "Follow along the Step by Step guide", - "spotify_cookie": "Spotify {name} Cookie", - "cookie_name_cookie": "{name} Cookie", - "fill_in_all_fields": "Please fill in all the fields", - "submit": "Submit", - "exit": "Exit", - "previous": "Previous", - "next": "Next", - "done": "Done", - "step_1": "Step 1", - "first_go_to": "First, Go to", - "login_if_not_logged_in": "and Login/Signup if you are not logged in", - "step_2": "Step 2", - "step_2_steps": "1. Once you're logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection", - "step_3": "Step 3", - "step_3_steps": "Copy the value of \"sp_dc\" Cookie", - "success_emoji": "Success🥳", - "success_message": "Now you've successfully Logged in with your Spotify account. Good Job, mate!", - "step_4": "Step 4", - "step_4_steps": "Paste the copied \"sp_dc\" value", - "something_went_wrong": "Something went wrong", - "piped_instance": "Piped Server Instance", - "piped_description": "The Piped server instance to use for track matching", - "piped_warning": "Some of them might not work well. So use at your own risk", - "invidious_instance": "Invidious Server Instance", - "invidious_description": "The Invidious server instance to use for track matching", - "invidious_warning": "Some of them might not work well. So use at your own risk", - "generate": "Generate", - "track_exists": "Track {track} already exists", - "replace_downloaded_tracks": "Replace all downloaded tracks", - "skip_download_tracks": "Skip downloading all downloaded tracks", - "do_you_want_to_replace": "Do you want to replace the existing track??", - "replace": "Replace", - "skip": "Skip", - "select_up_to_count_type": "Select up to {count} {type}", - "select_genres": "Select Genres", - "add_genres": "Add Genres", - "country": "Country", - "number_of_tracks_generate": "Number of tracks to generate", - "acousticness": "Acousticness", - "danceability": "Danceability", - "energy": "Energy", - "instrumentalness": "Instrumentalness", - "liveness": "Liveness", - "loudness": "Loudness", - "speechiness": "Speechiness", - "valence": "Valence", - "popularity": "Popularity", - "key": "Key", - "duration": "Duration (s)", - "tempo": "Tempo (BPM)", - "mode": "Mode", - "time_signature": "Time Signature", - "short": "Short", - "medium": "Medium", - "long": "Long", - "min": "Min", - "max": "Max", - "target": "Target", - "moderate": "Moderate", - "deselect_all": "Deselect All", - "select_all": "Select All", - "are_you_sure": "Are you sure?", - "generating_playlist": "Generating your custom playlist...", - "selected_count_tracks": "Selected {count} tracks", - "download_warning": "If you download all Tracks at bulk you're clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist's hard work", - "download_ip_ban_warning": "BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can't use YouTube (even if you're logged in) for at least 2-3 months from that IP device. And Spotube doesn't hold any responsibility if this ever happens", - "by_clicking_accept_terms": "By clicking 'accept' you agree to following terms:", - "download_agreement_1": "I know I'm pirating Music. I'm bad", - "download_agreement_2": "I'll support the Artist wherever I can and I'm only doing this because I don't have money to buy their art", - "download_agreement_3": "I'm completely aware that my IP can get blocked on YouTube & I don't hold Spotube or his owners/contributors responsible for any accidents caused by my current action", - "decline": "Decline", - "accept": "Accept", - "details": "Details", - "youtube": "YouTube", - "channel": "Channel", - "likes": "Likes", - "dislikes": "Dislikes", - "views": "Views", - "streamUrl": "Stream URL", - "stop": "Stop", - "sort_newest": "Sort by newest added", - "sort_oldest": "Sort by oldest added", - "sleep_timer": "Sleep Timer", - "mins": "{minutes} Minutes", - "hours": "{hours} Hours", - "hour": "{hours} Hour", - "custom_hours": "Custom Hours", - "logs": "Logs", - "developers": "Developers", - "not_logged_in": "You're not logged in", - "search_mode": "Search Mode", - "audio_source": "Audio Source", - "ok": "Ok", - "failed_to_encrypt": "Failed to encrypt", - "encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed", - "querying_info": "Querying info...", - "piped_api_down": "Piped API is down", - "piped_down_error_instructions": "The Piped instance {pipedInstance} is currently down\n\nEither change the instance or change the 'API type' to official YouTube API\n\nMake sure to restart the app after change", - "you_are_offline": "You are currently offline", - "connection_restored": "Your internet connection was restored", - "use_system_title_bar": "Use system title bar", - "crunching_results": "Crunching results...", - "search_to_get_results": "Search to get results", - "use_amoled_mode": "Pitch black dark theme", - "pitch_dark_theme": "AMOLED Mode", - "normalize_audio": "Normalize audio", - "change_cover": "Change cover", - "add_cover": "Add cover", - "restore_defaults": "Restore defaults", - "download_music_codec": "Download music codec", - "streaming_music_codec": "Streaming music codec", - "login_with_lastfm": "Login with Last.fm", - "connect": "Connect", - "disconnect_lastfm": "Disconnect Last.fm", - "disconnect": "Disconnect", - "username": "Username", - "password": "Password", - "login": "Login", - "login_with_your_lastfm": "Login with your Last.fm account", - "scrobble_to_lastfm": "Scrobble to Last.fm", - "go_to_album": "Go to Album", - "discord_rich_presence": "Discord Rich Presence", - "browse_all": "Browse All", - "genres": "Genres", - "explore_genres": "Explore Genres", - "friends": "Friends", - "no_lyrics_available": "Sorry, unable find lyrics for this track", - "start_a_radio": "Start a Radio", - "how_to_start_radio": "How do you want to start the radio?", - "replace_queue_question": "Do you want to replace the current queue or append to it?", - "endless_playback": "Endless Playback", - "delete_playlist": "Delete Playlist", - "delete_playlist_confirmation": "Are you sure you want to delete this playlist?", - "local_tracks": "Local Tracks", - "local_tab": "Local", - "song_link": "Song Link", - "skip_this_nonsense": "Skip this nonsense", - "freedom_of_music": "“Freedom of Music”", - "freedom_of_music_palm": "“Freedom of Music in the palm of your hand”", - "get_started": "Let's get started", - "youtube_source_description": "Recommended and works best.", - "piped_source_description": "Feeling free? Same as YouTube but a lot free.", - "jiosaavn_source_description": "Best for South Asian region.", - "invidious_source_description": "Similar to Piped but with higher availability.", - "highest_quality": "Highest Quality: {quality}", - "select_audio_source": "Select Audio Source", - "endless_playback_description": "Automatically append new songs\nto the end of the queue", - "choose_your_region": "Choose your region", - "choose_your_region_description": "This will help Spotube show you the right content\nfor your location.", - "choose_your_language": "Choose your language", - "help_project_grow": "Help this project grow", - "help_project_grow_description": "Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.", - "contribute_on_github": "Contribute on GitHub", - "donate_on_open_collective": "Donate on Open Collective", - "browse_anonymously": "Browse Anonymously", - "enable_connect": "Enable Connect", - "enable_connect_description": "Control Spotube from other devices", - "devices": "Devices", - "select": "Select", - "connect_client_alert": "You're being controlled by {client}", - "this_device": "This Device", - "remote": "Remote", - "stats": "Stats", - "and_n_more": "and {count} more", - "recently_played": "Recently Played", - "browse_more": "Browse More", - "no_title": "No Title", - "not_playing": "Not playing", - "epic_failure": "Epic failure!", - "added_num_tracks_to_queue": "Added {tracks_length} tracks to queue", - "spotube_has_an_update": "Spotube has an update", - "download_now": "Download Now", - "nightly_version": "Spotube Nightly {nightlyBuildNum} has been released", - "release_version": "Spotube v{version} has been released", - "read_the_latest": "Read the latest ", - "release_notes": "release notes", - "pick_color_scheme": "Pick color scheme", - "save": "Save", - "choose_the_device": "Choose the device:", - "multiple_device_connected": "There are multiple device connected.\nChoose the device you want this action to take place", - "nothing_found": "Nothing found", - "the_box_is_empty": "The box is empty", - "top_artists": "Top Artists", - "top_albums": "Top Albums", - "this_week": "This week", - "this_month": "This month", - "last_6_months": "Last 6 months", - "this_year": "This year", - "last_2_years": "Last 2 years", - "all_time": "All time", - "powered_by_provider": "Powered by {providerName}", - "email": "Email", - "profile_followers": "Followers", - "birthday": "Birthday", - "subscription": "Subscription", - "not_born": "Not born", - "hacker": "Hacker", - "profile": "Profile", - "no_name": "No Name", - "edit": "Edit", - "user_profile": "User Profile", - "count_plays": "{count} plays", - "streaming_fees_hypothetical": "Streaming fees (hypothetical)", - "minutes_listened": "Minutes listened", - "streamed_songs": "Streamed songs", - "count_streams": "{count} streams", - "owned_by_you": "Owned by you", - "copied_shareurl_to_clipboard": "Copied {shareUrl} to clipboard", - "spotify_hipotetical_calculation": "*This is calculated based on Spotify's per stream\npayout of $0.003 to $0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in Spotify.", - "count_mins": "{minutes} mins", - "summary_minutes": "minutes", - "summary_listened_to_music": "Listened to music", - "summary_songs": "songs", - "summary_streamed_overall": "Streamed overall", - "summary_owed_to_artists": "Owed to artists\nthis month", - "summary_artists": "artist's", - "summary_music_reached_you": "Music reached you", - "summary_full_albums": "full albums", - "summary_got_your_love": "Got your love", - "summary_playlists": "playlists", - "summary_were_on_repeat": "Were on repeat", - "total_money": "Total {money}", - "webview_not_found": "Webview not found", - "webview_not_found_description": "No webview runtime is installed in your device.\nIf it's installed make sure it's in the Environment PATH\n\nAfter installing, restart the app", - "unsupported_platform": "Unsupported platform", - "cache_music": "Cache music", - "open": "Open", - "cache_folder": "Cache folder", - "export": "Export", - "clear_cache": "Clear cache", - "clear_cache_confirmation": "Do you want to clear the cache?", - "export_cache_files": "Export Cached Files", - "found_n_files": "Found {count} files", - "export_cache_confirmation": "Do you want to export these files to", - "exported_n_out_of_m_files": "Exported {filesExported} out of {files} files", - "undo": "Undo", - "download_all": "Download all", - "add_all_to_playlist": "Add all to playlist", - "add_all_to_queue": "Add all to queue", - "play_all_next": "Play all next", - "pause": "Pause", - "view_all": "View all", - "no_tracks_added_yet": "Looks like you haven't added any tracks yet", - "no_tracks": "Looks like there are no tracks here", - "no_tracks_listened_yet": "Looks like you haven't listened to anything yet", - "not_following_artists": "You're not following any artists", - "no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet", - "no_logs_found": "No logs found", - "youtube_engine": "YouTube Engine", - "youtube_engine_not_installed_title": "{engine} is not installed", - "youtube_engine_not_installed_message": "{engine} is not installed in your system.", - "youtube_engine_set_path": "Make sure it's available in the PATH variable or\nset the absolute path to the {engine} executable below", - "youtube_engine_unix_issue_message": "In macOS/Linux/unix like OS's, setting path on .zshrc/.bashrc/.bash_profile etc. won't work.\nYou need to set the path in the shell configuration file", - "download": "Download", - "file_not_found": "File not found", - "custom": "Custom", - "add_custom_url": "Add custom URL", - "edit_port": "Edit port", - "port_helper_msg": "Default is -1 which indicates random number. If you've firewall configured, setting this is recommended.", - "connect_request": "Allow {client} to connect?", - "connection_request_denied": "Connection denied. User denied access." + "guest": "Guest", + "browse": "Browse", + "search": "Search", + "library": "Library", + "lyrics": "Lyrics", + "settings": "Settings", + "genre_categories_filter": "Filter categories or genres...", + "genre": "Genre", + "personalized": "Personalized", + "featured": "Featured", + "new_releases": "New Releases", + "songs": "Songs", + "playing_track": "Playing {track}", + "queue_clear_alert": "This will clear the current queue. {track_length} tracks will be removed\nDo you want to continue?", + "load_more": "Load more", + "playlists": "Playlists", + "artists": "Artists", + "albums": "Albums", + "tracks": "Tracks", + "downloads": "Downloads", + "filter_playlists": "Filter your playlists...", + "liked_tracks": "Liked Tracks", + "liked_tracks_description": "All your liked tracks", + "playlist": "Playlist", + "create_a_playlist": "Create a playlist", + "update_playlist": "Update playlist", + "create": "Create", + "cancel": "Cancel", + "update": "Update", + "playlist_name": "Playlist Name", + "name_of_playlist": "Name of the playlist", + "description": "Description", + "public": "Public", + "collaborative": "Collaborative", + "search_local_tracks": "Search local tracks...", + "play": "Play", + "delete": "Delete", + "none": "None", + "sort_a_z": "Sort by A-Z", + "sort_z_a": "Sort by Z-A", + "sort_artist": "Sort by Artist", + "sort_album": "Sort by Album", + "sort_duration": "Sort by Duration", + "sort_tracks": "Sort Tracks", + "currently_downloading": "Currently Downloading ({tracks_length})", + "cancel_all": "Cancel All", + "filter_artist": "Filter artists...", + "followers": "{followers} Followers", + "add_artist_to_blacklist": "Add artist to blacklist", + "top_tracks": "Top Tracks", + "fans_also_like": "Fans also like", + "loading": "Loading...", + "artist": "Artist", + "blacklisted": "Blacklisted", + "following": "Following", + "follow": "Follow", + "artist_url_copied": "Artist URL copied to clipboard", + "added_to_queue": "Added {tracks} tracks to queue", + "filter_albums": "Filter albums...", + "synced": "Synced", + "plain": "Plain", + "shuffle": "Shuffle", + "search_tracks": "Search tracks...", + "released": "Released", + "error": "Error {error}", + "title": "Title", + "time": "Time", + "more_actions": "More actions", + "download_count": "Download ({count})", + "add_count_to_playlist": "Add ({count}) to Playlist", + "add_count_to_queue": "Add ({count}) to Queue", + "play_count_next": "Play ({count}) next", + "album": "Album", + "copied_to_clipboard": "Copied {data} to clipboard", + "add_to_following_playlists": "Add {track} to following Playlists", + "add": "Add", + "added_track_to_queue": "Added {track} to queue", + "add_to_queue": "Add to queue", + "track_will_play_next": "{track} will play next", + "play_next": "Play next", + "removed_track_from_queue": "Removed {track} from queue", + "remove_from_queue": "Remove from queue", + "remove_from_favorites": "Remove from favorites", + "save_as_favorite": "Save as favorite", + "add_to_playlist": "Add to playlist", + "remove_from_playlist": "Remove from playlist", + "add_to_blacklist": "Add to blacklist", + "remove_from_blacklist": "Remove from blacklist", + "share": "Share", + "mini_player": "Mini Player", + "slide_to_seek": "Slide to seek forward or backward", + "shuffle_playlist": "Shuffle playlist", + "unshuffle_playlist": "Unshuffle playlist", + "previous_track": "Previous track", + "next_track": "Next track", + "pause_playback": "Pause Playback", + "resume_playback": "Resume Playback", + "loop_track": "Loop track", + "no_loop": "No loop", + "repeat_playlist": "Repeat playlist", + "queue": "Queue", + "alternative_track_sources": "Alternative track sources", + "download_track": "Download track", + "tracks_in_queue": "{tracks} tracks in queue", + "clear_all": "Clear all", + "show_hide_ui_on_hover": "Show/Hide UI on hover", + "always_on_top": "Always on top", + "exit_mini_player": "Exit Mini player", + "download_location": "Download location", + "local_library": "Local library", + "add_library_location": "Add to library", + "remove_library_location": "Remove from library", + "account": "Account", + "logout": "Logout", + "logout_of_this_account": "Logout of this account", + "language_region": "Language & Region", + "language": "Language", + "system_default": "System Default", + "market_place_region": "Marketplace Region", + "recommendation_country": "Recommendation Country", + "appearance": "Appearance", + "layout_mode": "Layout Mode", + "override_layout_settings": "Override responsive layout mode settings", + "adaptive": "Adaptive", + "compact": "Compact", + "extended": "Extended", + "theme": "Theme", + "dark": "Dark", + "light": "Light", + "system": "System", + "accent_color": "Accent Color", + "sync_album_color": "Sync album color", + "sync_album_color_description": "Uses the dominant color of the album art as the accent color", + "playback": "Playback", + "audio_quality": "Audio Quality", + "high": "High", + "low": "Low", + "pre_download_play": "Pre-download and play", + "pre_download_play_description": "Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)", + "skip_non_music": "Skip non-music segments (SponsorBlock)", + "blacklist_description": "Blacklisted tracks and artists", + "wait_for_download_to_finish": "Please wait for the current download to finish", + "desktop": "Desktop", + "close_behavior": "Close Behavior", + "close": "Close", + "minimize_to_tray": "Minimize to tray", + "show_tray_icon": "Show System tray icon", + "about": "About", + "u_love_spotube": "We know you love Spotube", + "check_for_updates": "Check for updates", + "about_spotube": "About Spotube", + "blacklist": "Blacklist", + "please_sponsor": "Please Sponsor/Donate", + "spotube_description": "Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept", + "version": "Version", + "build_number": "Build Number", + "founder": "Founder", + "repository": "Repository", + "bug_issues": "Bug+Issues", + "made_with": "Made with ❤️ in Bangladesh🇧🇩", + "kingkor_roy_tirtho": "Kingkor Roy Tirtho", + "copyright": "© 2021-{current_year} Kingkor Roy Tirtho", + "license": "License", + "credentials_will_not_be_shared_disclaimer": "Don't worry, any of your credentials won't be collected or shared with anyone", + "know_how_to_login": "Don't know how to do this?", + "follow_step_by_step_guide": "Follow along the Step by Step guide", + "cookie_name_cookie": "{name} Cookie", + "fill_in_all_fields": "Please fill in all the fields", + "submit": "Submit", + "exit": "Exit", + "previous": "Previous", + "next": "Next", + "done": "Done", + "step_1": "Step 1", + "first_go_to": "First, Go to", + "something_went_wrong": "Something went wrong", + "piped_instance": "Piped Server Instance", + "piped_description": "The Piped server instance to use for track matching", + "piped_warning": "Some of them might not work well. So use at your own risk", + "invidious_instance": "Invidious Server Instance", + "invidious_description": "The Invidious server instance to use for track matching", + "invidious_warning": "Some of them might not work well. So use at your own risk", + "generate": "Generate", + "track_exists": "Track {track} already exists", + "replace_downloaded_tracks": "Replace all downloaded tracks", + "skip_download_tracks": "Skip downloading all downloaded tracks", + "do_you_want_to_replace": "Do you want to replace the existing track??", + "replace": "Replace", + "skip": "Skip", + "select_up_to_count_type": "Select up to {count} {type}", + "select_genres": "Select Genres", + "add_genres": "Add Genres", + "country": "Country", + "number_of_tracks_generate": "Number of tracks to generate", + "acousticness": "Acousticness", + "danceability": "Danceability", + "energy": "Energy", + "instrumentalness": "Instrumentalness", + "liveness": "Liveness", + "loudness": "Loudness", + "speechiness": "Speechiness", + "valence": "Valence", + "popularity": "Popularity", + "key": "Key", + "duration": "Duration (s)", + "tempo": "Tempo (BPM)", + "mode": "Mode", + "time_signature": "Time Signature", + "short": "Short", + "medium": "Medium", + "long": "Long", + "min": "Min", + "max": "Max", + "target": "Target", + "moderate": "Moderate", + "deselect_all": "Deselect All", + "select_all": "Select All", + "are_you_sure": "Are you sure?", + "generating_playlist": "Generating your custom playlist...", + "selected_count_tracks": "Selected {count} tracks", + "download_warning": "If you download all Tracks at bulk you're clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist's hard work", + "download_ip_ban_warning": "BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can't use YouTube (even if you're logged in) for at least 2-3 months from that IP device. And Spotube doesn't hold any responsibility if this ever happens", + "by_clicking_accept_terms": "By clicking 'accept' you agree to following terms:", + "download_agreement_1": "I know I'm pirating Music. I'm bad", + "download_agreement_2": "I'll support the Artist wherever I can and I'm only doing this because I don't have money to buy their art", + "download_agreement_3": "I'm completely aware that my IP can get blocked on YouTube & I don't hold Spotube or his owners/contributors responsible for any accidents caused by my current action", + "decline": "Decline", + "accept": "Accept", + "details": "Details", + "youtube": "YouTube", + "channel": "Channel", + "likes": "Likes", + "dislikes": "Dislikes", + "views": "Views", + "streamUrl": "Stream URL", + "stop": "Stop", + "sort_newest": "Sort by newest added", + "sort_oldest": "Sort by oldest added", + "sleep_timer": "Sleep Timer", + "mins": "{minutes} Minutes", + "hours": "{hours} Hours", + "hour": "{hours} Hour", + "custom_hours": "Custom Hours", + "logs": "Logs", + "developers": "Developers", + "not_logged_in": "You're not logged in", + "search_mode": "Search Mode", + "audio_source": "Audio Source", + "ok": "Ok", + "failed_to_encrypt": "Failed to encrypt", + "encryption_failed_warning": "Spotube uses encryption to securely store your data. But failed to do so. So it'll fallback to insecure storage\nIf you're using linux, please make sure you've any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed", + "querying_info": "Querying info...", + "piped_api_down": "Piped API is down", + "piped_down_error_instructions": "The Piped instance {pipedInstance} is currently down\n\nEither change the instance or change the 'API type' to official YouTube API\n\nMake sure to restart the app after change", + "you_are_offline": "You are currently offline", + "connection_restored": "Your internet connection was restored", + "use_system_title_bar": "Use system title bar", + "crunching_results": "Crunching results...", + "search_to_get_results": "Search to get results", + "use_amoled_mode": "Pitch black dark theme", + "pitch_dark_theme": "AMOLED Mode", + "normalize_audio": "Normalize audio", + "change_cover": "Change cover", + "add_cover": "Add cover", + "restore_defaults": "Restore defaults", + "download_music_codec": "Download music codec", + "streaming_music_codec": "Streaming music codec", + "login_with_lastfm": "Login with Last.fm", + "connect": "Connect", + "disconnect_lastfm": "Disconnect Last.fm", + "disconnect": "Disconnect", + "username": "Username", + "password": "Password", + "login": "Login", + "login_with_your_lastfm": "Login with your Last.fm account", + "scrobble_to_lastfm": "Scrobble to Last.fm", + "go_to_album": "Go to Album", + "discord_rich_presence": "Discord Rich Presence", + "browse_all": "Browse All", + "genres": "Genres", + "explore_genres": "Explore Genres", + "friends": "Friends", + "no_lyrics_available": "Sorry, unable find lyrics for this track", + "start_a_radio": "Start a Radio", + "how_to_start_radio": "How do you want to start the radio?", + "replace_queue_question": "Do you want to replace the current queue or append to it?", + "endless_playback": "Endless Playback", + "delete_playlist": "Delete Playlist", + "delete_playlist_confirmation": "Are you sure you want to delete this playlist?", + "local_tracks": "Local Tracks", + "local_tab": "Local", + "song_link": "Song Link", + "skip_this_nonsense": "Skip this nonsense", + "freedom_of_music": "“Freedom of Music”", + "freedom_of_music_palm": "“Freedom of Music in the palm of your hand”", + "get_started": "Let's get started", + "youtube_source_description": "Recommended and works best.", + "piped_source_description": "Feeling free? Same as YouTube but a lot free.", + "jiosaavn_source_description": "Best for South Asian region.", + "invidious_source_description": "Similar to Piped but with higher availability.", + "highest_quality": "Highest Quality: {quality}", + "select_audio_source": "Select Audio Source", + "endless_playback_description": "Automatically append new songs\nto the end of the queue", + "choose_your_region": "Choose your region", + "choose_your_region_description": "This will help Spotube show you the right content\nfor your location.", + "choose_your_language": "Choose your language", + "help_project_grow": "Help this project grow", + "help_project_grow_description": "Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.", + "contribute_on_github": "Contribute on GitHub", + "donate_on_open_collective": "Donate on Open Collective", + "browse_anonymously": "Browse Anonymously", + "enable_connect": "Enable Connect", + "enable_connect_description": "Control Spotube from other devices", + "devices": "Devices", + "select": "Select", + "connect_client_alert": "You're being controlled by {client}", + "this_device": "This Device", + "remote": "Remote", + "stats": "Stats", + "and_n_more": "and {count} more", + "recently_played": "Recently Played", + "browse_more": "Browse More", + "no_title": "No Title", + "not_playing": "Not playing", + "epic_failure": "Epic failure!", + "added_num_tracks_to_queue": "Added {tracks_length} tracks to queue", + "spotube_has_an_update": "Spotube has an update", + "download_now": "Download Now", + "nightly_version": "Spotube Nightly {nightlyBuildNum} has been released", + "release_version": "Spotube v{version} has been released", + "read_the_latest": "Read the latest ", + "release_notes": "release notes", + "pick_color_scheme": "Pick color scheme", + "save": "Save", + "choose_the_device": "Choose the device:", + "multiple_device_connected": "There are multiple device connected.\nChoose the device you want this action to take place", + "nothing_found": "Nothing found", + "the_box_is_empty": "The box is empty", + "top_artists": "Top Artists", + "top_albums": "Top Albums", + "this_week": "This week", + "this_month": "This month", + "last_6_months": "Last 6 months", + "this_year": "This year", + "last_2_years": "Last 2 years", + "all_time": "All time", + "powered_by_provider": "Powered by {providerName}", + "email": "Email", + "profile_followers": "Followers", + "birthday": "Birthday", + "subscription": "Subscription", + "not_born": "Not born", + "hacker": "Hacker", + "profile": "Profile", + "no_name": "No Name", + "edit": "Edit", + "user_profile": "User Profile", + "count_plays": "{count} plays", + "streaming_fees_hypothetical": "Streaming fees (hypothetical)", + "minutes_listened": "Minutes listened", + "streamed_songs": "Streamed songs", + "count_streams": "{count} streams", + "owned_by_you": "Owned by you", + "copied_shareurl_to_clipboard": "Copied {shareUrl} to clipboard", + "hipotetical_calculation": "*This is calculated based on average online music streaming platform's per stream\npayout of $0.003 to $0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.", + "count_mins": "{minutes} mins", + "summary_minutes": "minutes", + "summary_listened_to_music": "Listened to music", + "summary_songs": "songs", + "summary_streamed_overall": "Streamed overall", + "summary_owed_to_artists": "Owed to artists\nthis month", + "summary_artists": "artist's", + "summary_music_reached_you": "Music reached you", + "summary_full_albums": "full albums", + "summary_got_your_love": "Got your love", + "summary_playlists": "playlists", + "summary_were_on_repeat": "Were on repeat", + "total_money": "Total {money}", + "webview_not_found": "Webview not found", + "webview_not_found_description": "No webview runtime is installed in your device.\nIf it's installed make sure it's in the Environment PATH\n\nAfter installing, restart the app", + "unsupported_platform": "Unsupported platform", + "cache_music": "Cache music", + "open": "Open", + "cache_folder": "Cache folder", + "export": "Export", + "clear_cache": "Clear cache", + "clear_cache_confirmation": "Do you want to clear the cache?", + "export_cache_files": "Export Cached Files", + "found_n_files": "Found {count} files", + "export_cache_confirmation": "Do you want to export these files to", + "exported_n_out_of_m_files": "Exported {filesExported} out of {files} files", + "undo": "Undo", + "download_all": "Download all", + "add_all_to_playlist": "Add all to playlist", + "add_all_to_queue": "Add all to queue", + "play_all_next": "Play all next", + "pause": "Pause", + "view_all": "View all", + "no_tracks_added_yet": "Looks like you haven't added any tracks yet", + "no_tracks": "Looks like there are no tracks here", + "no_tracks_listened_yet": "Looks like you haven't listened to anything yet", + "not_following_artists": "You're not following any artists", + "no_favorite_albums_yet": "Looks like you haven't added any albums to your favorites yet", + "no_logs_found": "No logs found", + "youtube_engine": "YouTube Engine", + "youtube_engine_not_installed_title": "{engine} is not installed", + "youtube_engine_not_installed_message": "{engine} is not installed in your system.", + "youtube_engine_set_path": "Make sure it's available in the PATH variable or\nset the absolute path to the {engine} executable below", + "youtube_engine_unix_issue_message": "In macOS/Linux/unix like OS's, setting path on .zshrc/.bashrc/.bash_profile etc. won't work.\nYou need to set the path in the shell configuration file", + "download": "Download", + "file_not_found": "File not found", + "custom": "Custom", + "add_custom_url": "Add custom URL", + "edit_port": "Edit port", + "port_helper_msg": "Default is -1 which indicates random number. If you've firewall configured, setting this is recommended.", + "connect_request": "Allow {client} to connect?", + "connection_request_denied": "Connection denied. User denied access." } diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index 35cafab8..f0ada331 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -827,18 +827,6 @@ abstract class AppLocalizations { /// **'Account'** String get account; - /// No description provided for @login_with_spotify. - /// - /// In en, this message translates to: - /// **'Login with your Spotify account'** - String get login_with_spotify; - - /// No description provided for @connect_with_spotify. - /// - /// In en, this message translates to: - /// **'Connect with Spotify'** - String get connect_with_spotify; - /// No description provided for @logout. /// /// In en, this message translates to: @@ -1082,7 +1070,7 @@ abstract class AppLocalizations { /// No description provided for @spotube_description. /// /// In en, this message translates to: - /// **'Spotube, a lightweight, cross-platform, free-for-all spotify client'** + /// **'Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept'** String get spotube_description; /// No description provided for @version. @@ -1139,12 +1127,6 @@ abstract class AppLocalizations { /// **'License'** String get license; - /// No description provided for @add_spotify_credentials. - /// - /// In en, this message translates to: - /// **'Add your spotify credentials to get started'** - String get add_spotify_credentials; - /// No description provided for @credentials_will_not_be_shared_disclaimer. /// /// In en, this message translates to: @@ -1163,12 +1145,6 @@ abstract class AppLocalizations { /// **'Follow along the Step by Step guide'** String get follow_step_by_step_guide; - /// No description provided for @spotify_cookie. - /// - /// In en, this message translates to: - /// **'Spotify {name} Cookie'** - String spotify_cookie(Object name); - /// No description provided for @cookie_name_cookie. /// /// In en, this message translates to: @@ -1223,60 +1199,6 @@ abstract class AppLocalizations { /// **'First, Go to'** String get first_go_to; - /// No description provided for @login_if_not_logged_in. - /// - /// In en, this message translates to: - /// **'and Login/Signup if you are not logged in'** - String get login_if_not_logged_in; - - /// No description provided for @step_2. - /// - /// In en, this message translates to: - /// **'Step 2'** - String get step_2; - - /// No description provided for @step_2_steps. - /// - /// In en, this message translates to: - /// **'1. Once you\'re logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection'** - String get step_2_steps; - - /// No description provided for @step_3. - /// - /// In en, this message translates to: - /// **'Step 3'** - String get step_3; - - /// No description provided for @step_3_steps. - /// - /// In en, this message translates to: - /// **'Copy the value of \"sp_dc\" Cookie'** - String get step_3_steps; - - /// No description provided for @success_emoji. - /// - /// In en, this message translates to: - /// **'Success🥳'** - String get success_emoji; - - /// No description provided for @success_message. - /// - /// In en, this message translates to: - /// **'Now you\'ve successfully Logged in with your Spotify account. Good Job, mate!'** - String get success_message; - - /// No description provided for @step_4. - /// - /// In en, this message translates to: - /// **'Step 4'** - String get step_4; - - /// No description provided for @step_4_steps. - /// - /// In en, this message translates to: - /// **'Paste the copied \"sp_dc\" value'** - String get step_4_steps; - /// No description provided for @something_went_wrong. /// /// In en, this message translates to: @@ -2411,11 +2333,11 @@ abstract class AppLocalizations { /// **'Copied {shareUrl} to clipboard'** String copied_shareurl_to_clipboard(Object shareUrl); - /// No description provided for @spotify_hipotetical_calculation. + /// No description provided for @hipotetical_calculation. /// /// In en, this message translates to: - /// **'*This is calculated based on Spotify\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in Spotify.'** - String get spotify_hipotetical_calculation; + /// **'*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'** + String get hipotetical_calculation; /// No description provided for @count_mins. /// diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index e713fe4e..d5ea314f 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -379,12 +379,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get account => 'حساب'; - @override - String get login_with_spotify => 'تسجيل الدخول بواسطة حساب Spotify'; - - @override - String get connect_with_spotify => 'توصيل بـSpotify'; - @override String get logout => 'تسجيل الخروج'; @@ -537,9 +531,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get license => 'الترخيص'; - @override - String get add_spotify_credentials => 'أضف بيانات Spotify الخاصة بك للبدء'; - @override String get credentials_will_not_be_shared_disclaimer => 'لا تقلق، لن يتم جمع أي من بيانات الخاصة بك أو مشاركتها مع أي شخص'; @@ -549,11 +540,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get follow_step_by_step_guide => 'اتبع الدليل خطوة بخطوة'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name كوكيز'; - } - @override String cookie_name_cookie(Object name) { return '$name كوكيز'; @@ -583,33 +569,6 @@ class AppLocalizationsAr extends AppLocalizations { @override String get first_go_to => 'أولا، اذهب إلى'; - @override - String get login_if_not_logged_in => 'وتسجيل الدخول/الاشتراك إذا لم تقم بتسجيل الدخول'; - - @override - String get step_2 => 'الخطوة 2'; - - @override - String get step_2_steps => '1. بمجرد تسجيل الدخول، اضغط على F12 أو انقر بزر الماوس الأيمن > فحص لفتح أدوات تطوير المتصفح.\n2. ثم انتقل إلى علامة التبويب \"التطبيقات\" (Chrome وEdge وBrave وما إلى ذلك.) أو علامة التبويب \"التخزين\" (Firefox وPalemoon وما إلى ذلك..)\n3. انتقل إلى قسم \"ملفات تعريف الارتباط\" ثم القسم الفرعي \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'الخطوة 3'; - - @override - String get step_3_steps => 'انسخ قيمة الكوكي \"sp_dc\"'; - - @override - String get success_emoji => 'نجاح 🥳'; - - @override - String get success_message => 'لقد قمت الآن بتسجيل الدخول بنجاح باستخدام حساب Spotify الخاص بك. عمل جيد يا صديقي!'; - - @override - String get step_4 => 'الخطوة 4'; - - @override - String get step_4_steps => 'الصق قيمة \"sp_dc\" المنسوخة'; - @override String get something_went_wrong => 'هناك خطأ ما'; @@ -1212,7 +1171,7 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*هذا محسوب بناءً على الدفع لكل بث من سبوتيفاي\nبقيمة 0.003 إلى 0.005 دولار. هذا حساب افتراضي\nلإعطاء المستخدم فكرة عن المبلغ الذي\nكان سيدفعه للفنانين إذا كانوا قد استمعوا\nإلى أغنيتهم على سبوتيفاي.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_bn.dart b/lib/l10n/generated/app_localizations_bn.dart index ae9c842a..575f1c6c 100644 --- a/lib/l10n/generated/app_localizations_bn.dart +++ b/lib/l10n/generated/app_localizations_bn.dart @@ -379,12 +379,6 @@ class AppLocalizationsBn extends AppLocalizations { @override String get account => 'অ্যাকাউন্ট'; - @override - String get login_with_spotify => 'আপনার Spotify account দিয়ে লগইন করুন'; - - @override - String get connect_with_spotify => 'Spotify লগইন'; - @override String get logout => 'লগআউট করুন'; @@ -537,9 +531,6 @@ class AppLocalizationsBn extends AppLocalizations { @override String get license => 'লাইসেন্স'; - @override - String get add_spotify_credentials => 'আপনার Spotify লগইন তথ্য যোগ করুন'; - @override String get credentials_will_not_be_shared_disclaimer => 'চিন্তা করবেন না, আপনার কোনো লগইন তথ্য সংগ্রহ করা হবে না বা কারো সাথে শেয়ার করা হবে না'; @@ -549,11 +540,6 @@ class AppLocalizationsBn extends AppLocalizations { @override String get follow_step_by_step_guide => 'ধাপে ধাপে নির্দেশিকা অনুসরণ করুন'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name কুকি'; - } - @override String cookie_name_cookie(Object name) { return '$name কুকি'; @@ -583,33 +569,6 @@ class AppLocalizationsBn extends AppLocalizations { @override String get first_go_to => 'প্রথমে যান'; - @override - String get login_if_not_logged_in => 'এবং যদি আপনি লগইন/সাইন-আপ না থাকেন তবে লগইন/সাইন-আপ করুন'; - - @override - String get step_2 => 'ধাপ 2'; - - @override - String get step_2_steps => '১. একবার আপনি লগ ইন করলে, ব্রাউজার ডেভটুল খুলতে F12 বা মাউসের রাইট ক্লিক > \"Inspect to open Browser DevTools\" টিপুন।\n২. তারপর \"Application\" ট্যাবে যান (Chrome, Edge, Brave etc..) অথবা \"Storage\" Tab (Firefox, Palemoon etc..)\n৩. \"Cookies \" বিভাগে যান তারপর \"https://accounts.spotify.com\" উপবিভাগে যান'; - - @override - String get step_3 => 'ধাপ 3'; - - @override - String get step_3_steps => 'কুকি \"sp_dc\" এর মানটি কপি করুন'; - - @override - String get success_emoji => 'আমরা সফল🥳'; - - @override - String get success_message => 'এখন আপনি সফলভাবে আপনার Spotify অ্যাকাউন্ট দিয়ে লগ ইন করেছেন। সাধুভাত আপনাকে'; - - @override - String get step_4 => 'ধাপ 4'; - - @override - String get step_4_steps => 'কপি করা \"sp_dc\" মানটি পেস্ট করুন'; - @override String get something_went_wrong => 'কিছু ভুল হয়েছে'; @@ -1212,7 +1171,7 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*এটি স্পোটিফাইয়ের প্রতি স্ট্রিম\n\$0.003 থেকে \$0.005 পেআউটের ভিত্তিতে গণনা করা হয়েছে। এটি একটি ধারণাগত\nগণনা ব্যবহারকারীদেরকে জানাতে দেয় যে কত টাকা\nতারা শিল্পীদের দিতো যদি তারা স্পোটিফাইতে\nতাদের গান শুনতেন।'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index 1e78b087..00207f89 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -379,12 +379,6 @@ class AppLocalizationsCa extends AppLocalizations { @override String get account => 'Compte'; - @override - String get login_with_spotify => 'Iniciar sesión amb el seu compte de Spotify'; - - @override - String get connect_with_spotify => 'Connectar amb Spotify'; - @override String get logout => 'Tancar sessió'; @@ -537,9 +531,6 @@ class AppLocalizationsCa extends AppLocalizations { @override String get license => 'Llicència'; - @override - String get add_spotify_credentials => 'Afegir les seves credencials de Spotify per començar'; - @override String get credentials_will_not_be_shared_disclaimer => 'No es preocupi, les seves credencials no seran recollides ni compartides amb ningú'; @@ -549,11 +540,6 @@ class AppLocalizationsCa extends AppLocalizations { @override String get follow_step_by_step_guide => 'Segueixi la guia pas a pas'; - @override - String spotify_cookie(Object name) { - return 'Cookie de Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsCa extends AppLocalizations { @override String get first_go_to => 'Primer, vagi a'; - @override - String get login_if_not_logged_in => 'i iniciï sessió/registri el seu compte si no ho ha fet encara'; - - @override - String get step_2 => 'Pas 2'; - - @override - String get step_2_steps => '1. Una vegada que hagi iniciat sessió, premi F12 o faci clic dret amb el ratolí > Inspeccionar per obrir les eines de desenvolulpador del navegador.\n2. Després vagi a la pestanya \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Vagi a la secció \"Cookies\" i després a la subsecció \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Pas 3'; - - @override - String get step_3_steps => 'Copia el valor de la cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Èxit! 🥳'; - - @override - String get success_message => 'Ara has iniciat sessió amb èxit al teu compte de Spotify. Bona feina!'; - - @override - String get step_4 => 'Pas 4'; - - @override - String get step_4_steps => 'Pega el valor copiado de \"sp_dc\"'; - @override String get something_went_wrong => 'Quelcom ha sortit malament'; @@ -1212,7 +1171,7 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Això es calcula basant-se en els\npagaments per reproducció de Spotify de \$0.003 a \$0.005.\nAquest és un càlcul hipotètic per\ndonar als usuaris una idea de quant\nhaurien pagat als artistes si haguessin escoltat\nla seva cançó a Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_cs.dart b/lib/l10n/generated/app_localizations_cs.dart index dcf39b70..a6e6e86d 100644 --- a/lib/l10n/generated/app_localizations_cs.dart +++ b/lib/l10n/generated/app_localizations_cs.dart @@ -379,12 +379,6 @@ class AppLocalizationsCs extends AppLocalizations { @override String get account => 'Účet'; - @override - String get login_with_spotify => 'Přihlásit se pomocí Spotify účtu'; - - @override - String get connect_with_spotify => 'Připojit k Spotify'; - @override String get logout => 'Odhlásit se'; @@ -537,9 +531,6 @@ class AppLocalizationsCs extends AppLocalizations { @override String get license => 'Licence'; - @override - String get add_spotify_credentials => 'Přidejte své přihlašovací údaje Spotify a začněte'; - @override String get credentials_will_not_be_shared_disclaimer => 'Nebojte, žádné z vašich údajů nebudou shromažďovány ani s nikým sdíleny'; @@ -549,11 +540,6 @@ class AppLocalizationsCs extends AppLocalizations { @override String get follow_step_by_step_guide => 'Postupujte podle návodu'; - @override - String spotify_cookie(Object name) { - return 'Cookie Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsCs extends AppLocalizations { @override String get first_go_to => 'Nejprve jděte na'; - @override - String get login_if_not_logged_in => 'a přihlašte se nebo se zaregistrujte, pokud nejste přihlášeni'; - - @override - String get step_2 => 'Krok 2'; - - @override - String get step_2_steps => '1. Jakmile jste přihlášeni, stiskněte F12 nebo pravé tlačítko myši > Prozkoumat, abyste otevřeli nástroje pro vývojáře prohlížeče.\n2. Poté přejděte na kartu \"Aplikace\" (Chrome, Edge, Brave atd.) nebo kartu \"Úložiště\" (Firefox, Palemoon atd.)\n3. Přejděte do sekce \"Cookies\" a pak do podsekce \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Krok 3'; - - @override - String get step_3_steps => 'Zkopírujte hodnotu cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Úspěch🥳'; - - @override - String get success_message => 'Nyní jste úspěšně přihlášeni pomocí svého Spotify účtu. Dobrá práce, kamaráde!'; - - @override - String get step_4 => 'Krok 4'; - - @override - String get step_4_steps => 'Vložte zkopírovanou hodnotu \"sp_dc\"'; - @override String get something_went_wrong => 'Něco se pokazilo'; @@ -1212,7 +1171,7 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Toto je vypočítáno na základě výplaty\nza stream Spotify od \$0.003 do \$0.005.\nToto je hypotetický výpočet,\nabyste měli představu o tom, kolik\nbyste zaplatili umělcům,\npokud byste poslouchali jejich píseň na Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 17b9eac4..7b1da343 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -379,12 +379,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get account => 'Konto'; - @override - String get login_with_spotify => 'Mit deinem Spotify-Konto anmelden'; - - @override - String get connect_with_spotify => 'Mit Spotify verbinden'; - @override String get logout => 'Abmelden'; @@ -537,9 +531,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get license => 'Lizenz'; - @override - String get add_spotify_credentials => 'Fügen Sie Ihre Spotify-Anmeldeinformationen hinzu, um zu starten'; - @override String get credentials_will_not_be_shared_disclaimer => 'Keine Sorge, Ihre Anmeldeinformationen werden nicht erfasst oder mit anderen geteilt'; @@ -549,11 +540,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get follow_step_by_step_guide => 'Befolgen Sie die schrittweise Anleitung'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsDe extends AppLocalizations { @override String get first_go_to => 'Gehe zuerst zu'; - @override - String get login_if_not_logged_in => 'und melde dich an/registriere dich, falls du nicht angemeldet bist'; - - @override - String get step_2 => 'Schritt 2'; - - @override - String get step_2_steps => '1. Wenn du angemeldet bist, drücke F12 oder klicke mit der rechten Maustaste > Inspektion, um die Browser-Entwicklertools zu öffnen.\n2. Gehe dann zum \"Anwendungs\"-Tab (Chrome, Edge, Brave usw.) oder zum \"Storage\"-Tab (Firefox, Palemoon usw.)\n3. Gehe zum Abschnitt \"Cookies\" und dann zum Unterabschnitt \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Schritt 3'; - - @override - String get step_3_steps => 'Kopiere den Wert des Cookies \"sp_dc\"'; - - @override - String get success_emoji => 'Erfolg🥳'; - - @override - String get success_message => 'Jetzt bist du erfolgreich mit deinem Spotify-Konto angemeldet. Gut gemacht, Kumpel!'; - - @override - String get step_4 => 'Schritt 4'; - - @override - String get step_4_steps => 'Füge den kopierten Wert von \"sp_dc\" ein'; - @override String get something_went_wrong => 'Etwas ist schiefgelaufen'; @@ -1212,7 +1171,7 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Dies ist basierend auf Spotifys\npro Stream Auszahlung von \$0,003 bis \$0,005\nberechnet. Dies ist eine hypothetische Berechnung,\num dem Benutzer Einblick zu geben,\nwieviel sie den Künstlern gezahlt hätten,\nwenn sie ihren Song auf Spotify gehört hätten.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index 91ac3586..d1f9ba0c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -379,12 +379,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get account => 'Account'; - @override - String get login_with_spotify => 'Login with your Spotify account'; - - @override - String get connect_with_spotify => 'Connect with Spotify'; - @override String get logout => 'Logout'; @@ -506,7 +500,7 @@ class AppLocalizationsEn extends AppLocalizations { String get please_sponsor => 'Please Sponsor/Donate'; @override - String get spotube_description => 'Spotube, a lightweight, cross-platform, free-for-all spotify client'; + String get spotube_description => 'Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept'; @override String get version => 'Version'; @@ -537,9 +531,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get license => 'License'; - @override - String get add_spotify_credentials => 'Add your spotify credentials to get started'; - @override String get credentials_will_not_be_shared_disclaimer => 'Don\'t worry, any of your credentials won\'t be collected or shared with anyone'; @@ -549,11 +540,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get follow_step_by_step_guide => 'Follow along the Step by Step guide'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsEn extends AppLocalizations { @override String get first_go_to => 'First, Go to'; - @override - String get login_if_not_logged_in => 'and Login/Signup if you are not logged in'; - - @override - String get step_2 => 'Step 2'; - - @override - String get step_2_steps => '1. Once you\'re logged in, press F12 or Mouse Right Click > Inspect to Open the Browser devtools.\n2. Then go the \"Application\" Tab (Chrome, Edge, Brave etc..) or \"Storage\" Tab (Firefox, Palemoon etc..)\n3. Go to the \"Cookies\" section then the \"https://accounts.spotify.com\" subsection'; - - @override - String get step_3 => 'Step 3'; - - @override - String get step_3_steps => 'Copy the value of \"sp_dc\" Cookie'; - - @override - String get success_emoji => 'Success🥳'; - - @override - String get success_message => 'Now you\'ve successfully Logged in with your Spotify account. Good Job, mate!'; - - @override - String get step_4 => 'Step 4'; - - @override - String get step_4_steps => 'Paste the copied \"sp_dc\" value'; - @override String get something_went_wrong => 'Something went wrong'; @@ -1212,7 +1171,7 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*This is calculated based on Spotify\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 5a05dc78..91026447 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -379,12 +379,6 @@ class AppLocalizationsEs extends AppLocalizations { @override String get account => 'Cuenta'; - @override - String get login_with_spotify => 'Iniciar sesión con tu cuenta de Spotify'; - - @override - String get connect_with_spotify => 'Conectar con Spotify'; - @override String get logout => 'Cerrar sesión'; @@ -537,9 +531,6 @@ class AppLocalizationsEs extends AppLocalizations { @override String get license => 'Licencia'; - @override - String get add_spotify_credentials => 'Agrega tus credenciales de Spotify para comenzar'; - @override String get credentials_will_not_be_shared_disclaimer => 'No te preocupes, tus credenciales no serán recopiladas ni compartidas con nadie'; @@ -549,11 +540,6 @@ class AppLocalizationsEs extends AppLocalizations { @override String get follow_step_by_step_guide => 'Sigue la guía paso a paso'; - @override - String spotify_cookie(Object name) { - return 'Cookie de Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsEs extends AppLocalizations { @override String get first_go_to => 'Primero, ve a'; - @override - String get login_if_not_logged_in => 'e inicia sesión/registra tu cuenta si no lo has hecho aún'; - - @override - String get step_2 => 'Paso 2'; - - @override - String get step_2_steps => '1. Una vez que hayas iniciado sesión, presiona F12 o haz clic derecho con el ratón > Inspeccionar para abrir las herramientas de desarrollo del navegador.\n2. Luego ve a la pestaña \"Application\" (Chrome, Edge, Brave, etc.) o \"Storage\" (Firefox, Palemoon, etc.)\n3. Ve a la sección \"Cookies\" y luego la subsección \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Paso 3'; - - @override - String get step_3_steps => 'Copia el valor de la cookie \"sp_dc\"'; - - @override - String get success_emoji => '¡Éxito! 🥳'; - - @override - String get success_message => 'Ahora has iniciado sesión con éxito en tu cuenta de Spotify. ¡Buen trabajo!'; - - @override - String get step_4 => 'Paso 4'; - - @override - String get step_4_steps => 'Pega el valor copiado de \"sp_dc\"'; - @override String get something_went_wrong => 'Algo salió mal'; @@ -1212,7 +1171,7 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Esto se calcula en base al\npago por stream de Spotify de \$0.003 a \$0.005.\nEs un cálculo hipotético para dar\nuna idea de cuánto habría\npagado a los artistas si hubieras escuchado\nsu canción en Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 6a5d81d7..18e92e03 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -379,12 +379,6 @@ class AppLocalizationsEu extends AppLocalizations { @override String get account => 'Kontua'; - @override - String get login_with_spotify => 'Hasi saioa zure Spotify kontuarekin'; - - @override - String get connect_with_spotify => 'Spotify-rekin konektatu'; - @override String get logout => 'Itxi saioa'; @@ -537,9 +531,6 @@ class AppLocalizationsEu extends AppLocalizations { @override String get license => 'Lizentzia'; - @override - String get add_spotify_credentials => 'Gehitu zure Spotify kredentzialak hasi ahal izateko'; - @override String get credentials_will_not_be_shared_disclaimer => 'Ez arduratu, zure kredentzialak ez ditugu bilduko edo inorekin elkarbanatuko'; @@ -549,11 +540,6 @@ class AppLocalizationsEu extends AppLocalizations { @override String get follow_step_by_step_guide => 'Jarraitu pausoz-pausoko gida'; - @override - String spotify_cookie(Object name) { - return 'Spotify-ren $name cookiea'; - } - @override String cookie_name_cookie(Object name) { return '$name cookiea'; @@ -583,33 +569,6 @@ class AppLocalizationsEu extends AppLocalizations { @override String get first_go_to => 'Hasteko, joan hona'; - @override - String get login_if_not_logged_in => 'eta hasi saioa/sortu kontua lehendik ez baduzu eginda'; - - @override - String get step_2 => '2. pausua'; - - @override - String get step_2_steps => '1. Saioa hasita duzularik, sakatu F12 edo saguaren eskuineko botoia klikatu > Ikuskatu nabigatzaileko garapen tresnak irekitzeko.\n2. Joan \"Aplikazio\" (Chrome, Edge, Brave, etab.) edo \"Biltegiratzea\" (Firefox, Palemoon, etab.)\n3. Joan \"Cookieak\" atalera eta gero \"https://accounts.spotify.com\" azpiatalera'; - - @override - String get step_3 => '3. pausua'; - - @override - String get step_3_steps => 'Kopiatu \"sp_dc\" cookiearen balioa'; - - @override - String get success_emoji => 'Eginda! 🥳'; - - @override - String get success_message => 'Ongi hasi duzu zure Spotify kontua. Lan bikaina, lagun!'; - - @override - String get step_4 => '4. pausua'; - - @override - String get step_4_steps => 'Itsatsi \"sp_dc\"-tik kopiatutako balioa'; - @override String get something_went_wrong => 'Zerbaitek huts egin du'; @@ -1212,7 +1171,7 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Sportify-k stream bakoitzeko duen \$0.003 eta \$0.005\nordainsarian oinarritua da. Kalkulu hipotetiko bat,\nkanta hauek Spotify-n entzun bazenitu,\nberaiek artistari zenbat ordaiduko lioketen jakin dezazun.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_fa.dart b/lib/l10n/generated/app_localizations_fa.dart index d3e13d97..1c0f9dfb 100644 --- a/lib/l10n/generated/app_localizations_fa.dart +++ b/lib/l10n/generated/app_localizations_fa.dart @@ -379,12 +379,6 @@ class AppLocalizationsFa extends AppLocalizations { @override String get account => 'حساب کاربری'; - @override - String get login_with_spotify => 'با حساب اسپوتیفای خود وارد شوید'; - - @override - String get connect_with_spotify => 'متصل شدن به اسپوتیفای'; - @override String get logout => 'خارج شدن'; @@ -537,9 +531,6 @@ class AppLocalizationsFa extends AppLocalizations { @override String get license => 'مجوز'; - @override - String get add_spotify_credentials => 'برای شروع اعتبار اسپوتیفای خود را اضافه کنید'; - @override String get credentials_will_not_be_shared_disclaimer => 'نگران نباشید هیچ کدوما از اعتبارات شما جمع اوری نمیشود یا با کسی اشتراک گزاشته نمیشود'; @@ -549,11 +540,6 @@ class AppLocalizationsFa extends AppLocalizations { @override String get follow_step_by_step_guide => 'راهنما را گام به گام دنبال کنید'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name کوکی'; - } - @override String cookie_name_cookie(Object name) { return '$name کوکی'; @@ -583,33 +569,6 @@ class AppLocalizationsFa extends AppLocalizations { @override String get first_go_to => 'اول برو داخل '; - @override - String get login_if_not_logged_in => 'و اگر وارد نشده اید، وارد/ثبت نام کنید'; - - @override - String get step_2 => 'گام 2'; - - @override - String get step_2_steps => '1. پس از ورود به سیستم، F12 یا کلیک راست ماوس > Inspect را فشار دهید تا ابزارهای توسعه مرورگر باز شود..\n2. سپس به تب \"Application\" (Chrome, Edge, Brave etc..) یا \"Storage\" Tab (Firefox, Palemoon etc..)\n3. به قسمت \"Cookies\" و به پخش \"https://accounts.spotify.com\" بروید'; - - @override - String get step_3 => 'گام 3'; - - @override - String get step_3_steps => 'مقدار کوکی \"sp_dc\" را کپی کنید'; - - @override - String get success_emoji => 'موفقیت🥳'; - - @override - String get success_message => 'اکنون با موفقیت با حساب اسپوتیفای خود وارد شده اید'; - - @override - String get step_4 => 'مرحله 4'; - - @override - String get step_4_steps => 'مقدار کپی شده \"sp_dc\" را الصاق کنید'; - @override String get something_went_wrong => 'اشتباهی رخ داده'; @@ -1212,7 +1171,7 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*این بر اساس پرداخت هر پخش اسپاتیفای\nبه مبلغ 0.003 تا 0.005 دلار محاسبه شده است.\nاین یک محاسبه فرضی است که به کاربران نشان دهد چقدر ممکن است\nبه هنرمندان پرداخت می‌کردند اگر ترانه آنها را در اسپاتیفای گوش می‌دادند.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_fi.dart b/lib/l10n/generated/app_localizations_fi.dart index f31b6609..5131fa41 100644 --- a/lib/l10n/generated/app_localizations_fi.dart +++ b/lib/l10n/generated/app_localizations_fi.dart @@ -379,12 +379,6 @@ class AppLocalizationsFi extends AppLocalizations { @override String get account => 'Käyttäjä'; - @override - String get login_with_spotify => 'Kirjaudu Spotify-käyttäjällä'; - - @override - String get connect_with_spotify => 'Yhdistä Spotify:lla'; - @override String get logout => 'Kirjaudu ulos'; @@ -537,9 +531,6 @@ class AppLocalizationsFi extends AppLocalizations { @override String get license => 'Lisenssi'; - @override - String get add_spotify_credentials => 'Lisää Spotify-tunnuksesi aloittaaksesi'; - @override String get credentials_will_not_be_shared_disclaimer => 'Älä huoli, tunnuksiasi ei talleteta tai jaeta kenenkään kanssa'; @@ -549,11 +540,6 @@ class AppLocalizationsFi extends AppLocalizations { @override String get follow_step_by_step_guide => 'Seuraa askel askeleelta opasta'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Keksi'; - } - @override String cookie_name_cookie(Object name) { return '$name Keksi'; @@ -583,33 +569,6 @@ class AppLocalizationsFi extends AppLocalizations { @override String get first_go_to => 'Ensiksi, mene'; - @override - String get login_if_not_logged_in => 'ja Kirjaudu/Tee tili jos et ole kirjautunut sisään'; - - @override - String get step_2 => 'Vaihe 2'; - - @override - String get step_2_steps => '1. Kun olet kirjautunut, paina F12 tai oikeaa hiiren näppäintä > Tarkista ja avaa selaimen kehittäjä työkalut.\n2. Mene sitten \"Application\"-välilehteen (Chrome, Edge, Brave jne..) tai \"Storage\"-välilehteen (Firefox, Palemoon jne..)\n3. Mene \"Cookies\"-osastoon, sitten \"https://accounts.spotify.com\" alakohtaan.'; - - @override - String get step_3 => 'Vaihe 3'; - - @override - String get step_3_steps => 'Kopioi Keksin \"sp_dc\" arvo'; - - @override - String get success_emoji => 'Onnistuit🥳'; - - @override - String get success_message => 'Olet nyt kirjautunut sisään Spotify-käyttäjällesi. Hyvää työtä toveri!'; - - @override - String get step_4 => 'Vaihe 4'; - - @override - String get step_4_steps => 'Liitä kopioitu \"sp_dc\" arvo'; - @override String get something_went_wrong => 'Jotain meni pieleen'; @@ -1212,7 +1171,7 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Tämä on laskettu Spotifyn suoratoiston\nmaksun perusteella, joka on 0,003–0,005 dollaria.\nTämä on hypoteettinen laskelma, joka antaa käyttäjälle käsityksen\nsiitä, kuinka paljon he olisivat maksaneet artisteille,\njollei heidän kappaleensa olisi kuunneltu Spotifyssa.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index 56caeae7..f1efd200 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -379,12 +379,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get account => 'Compte'; - @override - String get login_with_spotify => 'Se connecter avec votre compte Spotify'; - - @override - String get connect_with_spotify => 'Se connecter avec Spotify'; - @override String get logout => 'Se déconnecter'; @@ -537,9 +531,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get license => 'Licence'; - @override - String get add_spotify_credentials => 'Ajoutez vos identifiants Spotify pour commencer'; - @override String get credentials_will_not_be_shared_disclaimer => 'Ne vous inquiétez pas, vos identifiants ne seront ni collectés ni partagés avec qui que ce soit'; @@ -549,11 +540,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get follow_step_by_step_guide => 'Suivez le guide étape par étape'; - @override - String spotify_cookie(Object name) { - return 'Cookie Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsFr extends AppLocalizations { @override String get first_go_to => 'Tout d\'abord, allez sur'; - @override - String get login_if_not_logged_in => 'et connectez-vous/inscrivez-vous si vous n\'êtes pas connecté'; - - @override - String get step_2 => 'Étape 2'; - - @override - String get step_2_steps => '1. Une fois connecté, appuyez sur F12 ou clic droit de la souris > Inspecter pour ouvrir les outils de développement du navigateur.\n2. Ensuite, allez dans l\'onglet \"Application\" (Chrome, Edge, Brave, etc.) ou l\'onglet \"Stockage\" (Firefox, Palemoon, etc.)\n3. Allez dans la section \"Cookies\", puis dans la sous-section \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Étape 3'; - - @override - String get step_3_steps => 'Copiez la valeur du cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Succès🥳'; - - @override - String get success_message => 'Vous êtes maintenant connecté avec succès à votre compte Spotify. Bon travail, mon ami!'; - - @override - String get step_4 => 'Étape 4'; - - @override - String get step_4_steps => 'Collez la valeur copiée de \"sp_dc\"'; - @override String get something_went_wrong => 'Quelque chose s\'est mal passé'; @@ -1212,7 +1171,7 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Cela est calculé en fonction du\npaiement par stream de Spotify de 0,003 \$ à 0,005 \$.\nIl s\'agit d\'un calcul hypothétique pour donner\nune idée de combien vous auriez\npayé aux artistes si vous aviez\nécouté leur chanson sur Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index 440868f5..bacebff6 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -379,12 +379,6 @@ class AppLocalizationsHi extends AppLocalizations { @override String get account => 'खाता'; - @override - String get login_with_spotify => 'अपने Spotify खाते से लॉग इन करें'; - - @override - String get connect_with_spotify => 'Spotify से कनेक्ट करें'; - @override String get logout => 'लॉगआउट'; @@ -537,9 +531,6 @@ class AppLocalizationsHi extends AppLocalizations { @override String get license => 'लाइसेंस'; - @override - String get add_spotify_credentials => 'शुरू होने के लिए अपने स्पॉटिफाई क्रेडेंशियल जोड़ें'; - @override String get credentials_will_not_be_shared_disclaimer => 'चिंता न करें, आपके क्रेडेंशियल किसी भी तरह से नहीं एकत्रित या साझा किए जाएंगे'; @@ -549,11 +540,6 @@ class AppLocalizationsHi extends AppLocalizations { @override String get follow_step_by_step_guide => 'कदम से कदम गाइड के साथ चलें'; - @override - String spotify_cookie(Object name) { - return 'स्पॉटिफाई $name कुकी'; - } - @override String cookie_name_cookie(Object name) { return '$name कुकी'; @@ -583,33 +569,6 @@ class AppLocalizationsHi extends AppLocalizations { @override String get first_go_to => 'पहले, जाएं'; - @override - String get login_if_not_logged_in => 'और यदि आप लॉगिन नहीं हैं तो लॉगिन / साइनअप करें'; - - @override - String get step_2 => '2 चरण'; - - @override - String get step_2_steps => '1. जब आप लॉगिन हो जाएँ, तो F12 दबाएं या माउस राइट क्लिक> निरीक्षण करें ताकि ब्राउज़र डेवटूल्स खुलें।\n2. फिर ब्राउज़र के \"एप्लिकेशन\" टैब (Chrome, Edge, Brave आदि) या \"स्टोरेज\" टैब (Firefox, Palemoon आदि) में जाएं\n3. \"कुकीज़\" अनुभाग में जाएं फिर \"https: //accounts.spotify.com\" उप-अनुभाग में जाएं'; - - @override - String get step_3 => 'स्टेप 3'; - - @override - String get step_3_steps => '\"sp_dc\" कुकी का मूल्य कॉपी करें'; - - @override - String get success_emoji => 'सफलता🥳'; - - @override - String get success_message => 'अब आप अपने स्पॉटिफाई अकाउंट से सफलतापूर्वक लॉगइन हो गए हैं। अच्छा काम किया!'; - - @override - String get step_4 => 'स्टेप 4'; - - @override - String get step_4_steps => 'कॉपी किए गए \"sp_dc\" मूल्य को पेस्ट करें'; - @override String get something_went_wrong => 'कुछ गलत हो गया'; @@ -1212,7 +1171,7 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*यो Spotify को प्रति स्ट्रीम भुगतानको आधारमा\n\$0.003 देखि \$0.005 को बीचमा गणना गरिएको हो। यो एक काल्पनिक\nगणना हो जसले प्रयोगकर्तालाई देखाउँछ कि उनीहरूले कति\nअर्टिस्टहरूलाई तिनीहरूका गीतहरू Spotify मा सुनेमा\nभुक्तान गर्नुपर्ने थियो।'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index d76adc71..851af46d 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -379,12 +379,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get account => 'Akun'; - @override - String get login_with_spotify => 'Masuk dengan Spotify'; - - @override - String get connect_with_spotify => 'Hubungkan dengan Spotify'; - @override String get logout => 'Keluar'; @@ -537,9 +531,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get license => 'Lisensi'; - @override - String get add_spotify_credentials => 'Tambahkan kredensial Spotify Anda untuk memulai'; - @override String get credentials_will_not_be_shared_disclaimer => 'Jangan khawatir, kredensial Anda tidak akan dikumpulkan atau dibagikan kepada siapa pun'; @@ -549,11 +540,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get follow_step_by_step_guide => 'Ikuti panduan Langkah demi Langkah'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsId extends AppLocalizations { @override String get first_go_to => 'Pertama, Pergi ke'; - @override - String get login_if_not_logged_in => 'dan Masuk/Daftar jika Anda belum masuk'; - - @override - String get step_2 => 'Langkah 2'; - - @override - String get step_2_steps => '1. Setelah Anda masuk, tekan F12 atau Klik Kanan Mouse > Buka Browser Devtools.\n2. Lalu buka Tab \"Aplikasi\" (Chrome, Edge, Brave, dll.) atau Tab \"Penyimpanan\" (Firefox, Palemoon, dll.)\n3. Buka bagian \"Cookie\" lalu subbagian \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Langkah 3'; - - @override - String get step_3_steps => 'Salin nilai Cookie \"sp_dc\" '; - - @override - String get success_emoji => 'Berhasil🥳'; - - @override - String get success_message => 'Sekarang Anda telah berhasil Masuk dengan akun Spotify Anda. Kerja bagus, sobat!'; - - @override - String get step_4 => 'Langkah 4'; - - @override - String get step_4_steps => 'Tempel nilai \"sp_dc\" yang disalin'; - @override String get something_went_wrong => 'Terjadi kesalahan'; @@ -1212,7 +1171,7 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Ini dihitung berdasarkan pembayaran\nper stream Spotify dari \$0,003 hingga \$0,005.\nIni adalah perhitungan hipotetis untuk memberi\npengguna gambaran tentang berapa banyak\nmereka akan membayar kepada artis jika\nmereka mendengarkan lagu mereka di Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 82574b07..52f8fa98 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -379,12 +379,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get account => 'Account'; - @override - String get login_with_spotify => 'Login con il tuo account Spotify'; - - @override - String get connect_with_spotify => 'Connetti con Spotify'; - @override String get logout => 'Esci'; @@ -537,9 +531,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get license => 'Licenza'; - @override - String get add_spotify_credentials => 'Aggiungi le tue credenziali spotify per iniziare'; - @override String get credentials_will_not_be_shared_disclaimer => 'Non ti preoccupare, le tue credenziali non saranno inviate o condivise con nessuno'; @@ -549,11 +540,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get follow_step_by_step_guide => 'Segui la guida passo-passo'; - @override - String spotify_cookie(Object name) { - return 'Cookie Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsIt extends AppLocalizations { @override String get first_go_to => 'Prim, vai a'; - @override - String get login_if_not_logged_in => 'ed effettua il login o iscrizione se non sei già acceduto'; - - @override - String get step_2 => 'Passo 2'; - - @override - String get step_2_steps => '1. Quando sei acceduto premi F12 o premi il tasto destro del Mouse > Ispeziona per aprire gli strumenti di sviluppo del browser.\n2. Vai quindi nel tab \"Applicazione\" (Chrome, Edge, Brave etc..) o tab \"Archiviazione\" (Firefox, Palemoon etc..)\n3. Vai nella sezione \"Cookies\" quindi nella sezione \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Passo 3'; - - @override - String get step_3_steps => 'Copia il valore del cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Successo🥳'; - - @override - String get success_message => 'Ora hai correttamente effettuato il login al tuo account Spotify. Bel lavoro, amico!'; - - @override - String get step_4 => 'Passo 4'; - - @override - String get step_4_steps => 'Incolla il valore copiato di \"sp_dc\"'; - @override String get something_went_wrong => 'Qualcosa è andato storto'; @@ -1212,7 +1171,7 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Questo è calcolato in base al pagamento per streaming di Spotify\nche va da \$0.003 a \$0.005. Questo è un calcolo ipotetico\nper dare all\'utente un\'idea di quanto avrebbe pagato agli artisti se avesse ascoltato\ne loro canzoni su Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index b2f1a23d..a8491c68 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -379,12 +379,6 @@ class AppLocalizationsJa extends AppLocalizations { @override String get account => 'アカウント'; - @override - String get login_with_spotify => 'Spotify アカウントでログイン'; - - @override - String get connect_with_spotify => 'Spotify に接続'; - @override String get logout => 'ログアウト'; @@ -537,9 +531,6 @@ class AppLocalizationsJa extends AppLocalizations { @override String get license => 'ライセンス'; - @override - String get add_spotify_credentials => 'Spotify のログイン情報を追加してはじめましょう'; - @override String get credentials_will_not_be_shared_disclaimer => '心配ありません。個人情報を収集したり、共有されることはありません'; @@ -549,11 +540,6 @@ class AppLocalizationsJa extends AppLocalizations { @override String get follow_step_by_step_guide => 'やり方の説明を見る'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookies'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookies'; @@ -583,33 +569,6 @@ class AppLocalizationsJa extends AppLocalizations { @override String get first_go_to => '最初にここを開き'; - @override - String get login_if_not_logged_in => '、ログインしてないならログインまたは登録します'; - - @override - String get step_2 => 'ステップ 2'; - - @override - String get step_2_steps => '1. ログインしたら、F12を押すか、マウス右クリック > 調査(検証)でブラウザの開発者ツール (devtools) を開きます。\n2. アプリケーション (Application) タブ (Chrome, Edge, Brave など) またはストレージタブ (Firefox, Palemoon など)\n3. Cookies 欄を選択し、https://accounts.spotify.com の枝を選びます'; - - @override - String get step_3 => 'ステップ 3'; - - @override - String get step_3_steps => '\"sp_dc\" Cookieの値をコピー'; - - @override - String get success_emoji => '成功🥳'; - - @override - String get success_message => 'アカウントへのログインに成功しました。よくできました!'; - - @override - String get step_4 => 'ステップ 4'; - - @override - String get step_4_steps => 'コピーした\"sp_dc\"の値を貼り付け'; - @override String get something_went_wrong => '何か誤りがあります'; @@ -1212,7 +1171,7 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*これは、Spotifyのストリームごとの支払い\nが \$0.003 から \$0.005 の範囲で計算されています。これは仮想的な\n計算で、Spotify で曲を聴いた場合に、アーティストに\nどれくらい支払ったかをユーザーに示すためのものです。'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ka.dart b/lib/l10n/generated/app_localizations_ka.dart index 0044d144..36954dc5 100644 --- a/lib/l10n/generated/app_localizations_ka.dart +++ b/lib/l10n/generated/app_localizations_ka.dart @@ -379,12 +379,6 @@ class AppLocalizationsKa extends AppLocalizations { @override String get account => 'ანგარიში'; - @override - String get login_with_spotify => 'შედით თქვენი Spotify ანგარიშით'; - - @override - String get connect_with_spotify => 'დაუკავშირდით Spotify-ს'; - @override String get logout => 'გასვლა'; @@ -537,9 +531,6 @@ class AppLocalizationsKa extends AppLocalizations { @override String get license => 'ლიცენზია'; - @override - String get add_spotify_credentials => 'დასაწყებად დაამატეთ თქვენი Spotify მონაცემები'; - @override String get credentials_will_not_be_shared_disclaimer => 'არ ინერვიულოთ, თქვენი მონაცემები არ იქნება შეგროვებული ან გაზიარებული ვინმესთან'; @@ -549,11 +540,6 @@ class AppLocalizationsKa extends AppLocalizations { @override String get follow_step_by_step_guide => 'მიჰყევით ნაბიჯ-ნაბიჯ სახელმძღვანელოს'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name ქუქი'; - } - @override String cookie_name_cookie(Object name) { return '$name ქუქი'; @@ -583,33 +569,6 @@ class AppLocalizationsKa extends AppLocalizations { @override String get first_go_to => 'პირველი, გადადით'; - @override - String get login_if_not_logged_in => 'და შესვლა/რეგისტრაცია, თუ არ ხართ შესული'; - - @override - String get step_2 => 'ნაბიჯი 2'; - - @override - String get step_2_steps => '1. როცა შეხვალთ, დააჭირეთ F12-ს ან მაუსის მარჯვენა ღილაკს > Inspect to Open the Browser devtools.\n2. შემდეგ გახსენით \"Application\" განყოფილება (Chrome, Edge, Brave etc..) ან \"Storage\" განყოფილება (Firefox, Palemoon etc..)\n3. შედით \"Cookies\" სექციაში და შემდეგ \"https://accounts.spotify.com\" სუბსექციაში'; - - @override - String get step_3 => 'ნაბიჯი 3'; - - @override - String get step_3_steps => 'დააკოპირეთ \"sp_dc\" ქუქი-ფაილის მნიშვნელობა'; - - @override - String get success_emoji => 'წარმატება🥳'; - - @override - String get success_message => 'თქვენ წარმატებით შეხვედით თქვენი Spotify ანგარიშით.'; - - @override - String get step_4 => 'ნაბიჯი 4'; - - @override - String get step_4_steps => 'ჩასვით კოპირებული \"sp_dc\" მნიშვნელობა'; - @override String get something_went_wrong => 'Რაღაც არასწორად წავიდა'; @@ -1212,7 +1171,7 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*ეს გამოითვლება Spotify-ის თითოეულ სტრიმზე\nგადახდის შესაბამისად, რომელიც \$0.003 დან \$0.005-მდეა. ეს არის ჰიპოთეტური\nგამოთვლა, რომელიც აჩვენებს მომხმარებელს რამდენი გადაიხდიდა\nარტისტებს, თუკი ისინი უსმენდნენ მათ სიმღერებს Spotify-ზე.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index a3f11040..e7fa9abd 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -379,12 +379,6 @@ class AppLocalizationsKo extends AppLocalizations { @override String get account => '계정'; - @override - String get login_with_spotify => 'Spotify 계정으로 로그인'; - - @override - String get connect_with_spotify => 'Spotify에 연결'; - @override String get logout => '로그아웃'; @@ -537,9 +531,6 @@ class AppLocalizationsKo extends AppLocalizations { @override String get license => '라이선스'; - @override - String get add_spotify_credentials => '먼저 Spotify의 로그인정보를 추가하기'; - @override String get credentials_will_not_be_shared_disclaimer => '걱정마세요. 개인정보를 수집하거나 공유하지 않습니다.'; @@ -549,11 +540,6 @@ class AppLocalizationsKo extends AppLocalizations { @override String get follow_step_by_step_guide => '사용법 확인하기'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookies'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookies'; @@ -583,33 +569,6 @@ class AppLocalizationsKo extends AppLocalizations { @override String get first_go_to => '가장 먼저 먼저 들어갈 곳은 '; - @override - String get login_if_not_logged_in => '그리고 로그인을 하지 않았다면 로그인해주세요'; - - @override - String get step_2 => '2단계'; - - @override - String get step_2_steps => '1. 로그인에 성공하면、F12나 마우스 우클릭 > 검사(Inspect)을 눌러 브라우저의 개발자 도구(devtools)를 열어주세요.\n2. 애플리케이션 (Application) 탭 (Chrome, Edge, Brave 등) 또는 스토리지 탭 (Firefox, Palemoon 등)을 열어주세요.\n3. 쿠키 (Cookies) 섹션으로 들어가서, https://accounts.spotify.com 서브섹션으로 들어가주세요.'; - - @override - String get step_3 => '3단계'; - - @override - String get step_3_steps => '\"sp_dc\" 쿠키의 값을 복사'; - - @override - String get success_emoji => '성공🥳'; - - @override - String get success_message => '성공적으로 스포티파이 게정으로 로그인했습니다. 잘했어요!'; - - @override - String get step_4 => '4단계'; - - @override - String get step_4_steps => '복사한 \"sp_dc\"값을 붙여넣기'; - @override String get something_went_wrong => '알 수 없는 이유로 동작에 실패했습니다.'; @@ -1212,7 +1171,7 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Spotify의 스트림당 지불금 \$0.003에서 \$0.005까지의\n기준으로 계산되었습니다. 이는 사용자가 Spotify에서\n곡을 들을 때 아티스트에게 얼마를 지불했을지를\n알려주기 위한 가상의 계산입니다.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ne.dart b/lib/l10n/generated/app_localizations_ne.dart index f1d0f657..bad7b05e 100644 --- a/lib/l10n/generated/app_localizations_ne.dart +++ b/lib/l10n/generated/app_localizations_ne.dart @@ -379,12 +379,6 @@ class AppLocalizationsNe extends AppLocalizations { @override String get account => 'खाता'; - @override - String get login_with_spotify => 'तपाईंको Spotify खातासँग लगइन गर्नुहोस्'; - - @override - String get connect_with_spotify => 'Spotify सँग जडान गर्नुहोस्'; - @override String get logout => 'बाहिर निस्कनुहोस्'; @@ -537,9 +531,6 @@ class AppLocalizationsNe extends AppLocalizations { @override String get license => 'लाइसेन्स'; - @override - String get add_spotify_credentials => 'सुरु हुनका लागि तपाईंको स्पटिफाई क्रेडेन्शियल थप्नुहोस्'; - @override String get credentials_will_not_be_shared_disclaimer => 'चिन्ता नगर्नुहोस्, तपाईंको कुनै पनि क्रेडेन्शियलहरूले कसैले संग्रह वा साझा गर्नेछैन'; @@ -549,11 +540,6 @@ class AppLocalizationsNe extends AppLocalizations { @override String get follow_step_by_step_guide => 'चरणबद्ध मार्गदर्शनमा साथी बनाउनुहोस्'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name कुकी'; - } - @override String cookie_name_cookie(Object name) { return '$name कुकी'; @@ -583,33 +569,6 @@ class AppLocalizationsNe extends AppLocalizations { @override String get first_go_to => 'पहिलो, जानुहोस्'; - @override - String get login_if_not_logged_in => 'र लगइन/साइनअप गर्नुहोस् जुन तपाईंले लगइन गरेनन्'; - - @override - String get step_2 => 'कदम 2'; - - @override - String get step_2_steps => '1. एकबार तपाईं लगइन गरे पछि, F12 थिच्नुहोस् वा माउस राइट क्लिक गर्नुहोस् > इन्स्पेक्ट गर्नुहोस् भने ब्राउजर डेभटुलहरू खुलाउनका लागि।\n2. तपाईंको \"एप्लिकेसन\" ट्याबमा जानुहोस् (Chrome, Edge, Brave इत्यादि) वा \"स्टोरेज\" ट्याबमा जानुहोस् (Firefox, Palemoon इत्यादि)\n3. तपाईंको इन्सेक्ट गरेको ब्राउजर डेभटुलहरूमा \"कुकीहरू\" खण्डमा जानुहोस् अनि \"https://accounts.spotify.com\" उपकोणमा जानुहोस्'; - - @override - String get step_3 => 'कदम 3'; - - @override - String get step_3_steps => '\"sp_dc\" र \"sp_key\" (वा sp_gaid) कुकीहरूको मानहरू प्रतिलिपि गर्नुहोस्'; - - @override - String get success_emoji => 'सफलता 🥳'; - - @override - String get success_message => 'हाम्रो सानो भाइ, अब तपाईं सफलतापूर्वक आफ्नो Spotify खातामा लगइन गरेका छौं। राम्रो काम गरेको!'; - - @override - String get step_4 => 'कदम 4'; - - @override - String get step_4_steps => 'प्रतिलिपि गरेको \"sp_dc\" र \"sp_key\" (वा sp_gaid) मानहरूलाई आफ्नो ठाउँमा पेस्ट गर्नुहोस्'; - @override String get something_went_wrong => 'केहि गल्ति भएको छ'; @@ -1212,7 +1171,7 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*यो Spotify को प्रति स्ट्रीम भुगतानको आधारमा\n\$0.003 देखि \$0.005 को बीचमा गणना गरिएको हो। यो एक काल्पनिक\nगणना हो जसले प्रयोगकर्तालाई देखाउँछ कि उनीहरूले कति\nअर्टिस्टहरूलाई तिनीहरूका गीतहरू Spotify मा सुनेमा\nभुक्तान गर्नुपर्ने थियो।'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 338a664a..82d246c4 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -379,12 +379,6 @@ class AppLocalizationsNl extends AppLocalizations { @override String get account => 'Account'; - @override - String get login_with_spotify => 'Inloggen met je Spotify-account'; - - @override - String get connect_with_spotify => 'Verbinden met Spotify'; - @override String get logout => 'Afmelden'; @@ -537,9 +531,6 @@ class AppLocalizationsNl extends AppLocalizations { @override String get license => 'Licentie'; - @override - String get add_spotify_credentials => 'Voeg om te beginnen je spotify-aanmeldgegevens toe'; - @override String get credentials_will_not_be_shared_disclaimer => 'Maak je geen zorgen, je gegevens worden niet verzameld of gedeeld met anderen.'; @@ -549,11 +540,6 @@ class AppLocalizationsNl extends AppLocalizations { @override String get follow_step_by_step_guide => 'Volg de stapsgewijze handleiding'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsNl extends AppLocalizations { @override String get first_go_to => 'Ga eerst naar'; - @override - String get login_if_not_logged_in => 'en Inloggen/Aanmelden als je niet bent ingelogd'; - - @override - String get step_2 => 'Stap 2'; - - @override - String get step_2_steps => '1. Zodra je bent aangemeld, druk je op F12 of klik je met de rechtermuisknop > Inspect om de Browser devtools te openen.\n2. Ga vervolgens naar het tabblad \"Toepassing\" (Chrome, Edge, Brave enz..) of naar het tabblad \"Opslag\" (Firefox, Palemoon enz..).\n3. Ga naar de sectie \"Cookies\" en vervolgens naar de subsectie \"https://accounts.spotify.com\".'; - - @override - String get step_3 => 'Stap 3'; - - @override - String get step_3_steps => 'De waarde van cookie \"sp_dc\" kopiëren'; - - @override - String get success_emoji => 'Succes🥳'; - - @override - String get success_message => 'Je bent nu ingelogd met je Spotify account. Goed gedaan!'; - - @override - String get step_4 => 'Stap 4'; - - @override - String get step_4_steps => 'De gekopieerde waarde \"sp_dc\" plakken'; - @override String get something_went_wrong => 'Er ging iets mis'; @@ -1212,7 +1171,7 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Dit is berekend op basis van Spotify\'s betaling per stream\nvan \$0.003 tot \$0.005. Dit is een hypothetische\nberekening om de gebruiker inzicht te geven in hoeveel ze\naan de artiesten zouden hebben betaald als ze hun liedjes op Spotify\nzouden luisteren.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 00e8cb9c..70f253ab 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -379,12 +379,6 @@ class AppLocalizationsPl extends AppLocalizations { @override String get account => 'Konto'; - @override - String get login_with_spotify => 'Zaloguj się używając konta Spotify'; - - @override - String get connect_with_spotify => 'Połącz z Spotify'; - @override String get logout => 'Wyloguj'; @@ -537,9 +531,6 @@ class AppLocalizationsPl extends AppLocalizations { @override String get license => 'Licencja'; - @override - String get add_spotify_credentials => 'Dodaj swoje dane logowania Spotify, aby zacząć'; - @override String get credentials_will_not_be_shared_disclaimer => 'Nie martw się, żadne dane logowania nie są zbierane ani udostępniane nikomu'; @@ -549,11 +540,6 @@ class AppLocalizationsPl extends AppLocalizations { @override String get follow_step_by_step_guide => 'Postępuj zgodnie z poradnikiem krok po kroku'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Ciasteczko'; - } - @override String cookie_name_cookie(Object name) { return '$name Ciasteczko'; @@ -583,33 +569,6 @@ class AppLocalizationsPl extends AppLocalizations { @override String get first_go_to => 'Po pierwsze przejdź do'; - @override - String get login_if_not_logged_in => 'i Zaloguj się/Zarejestruj jeśli nie jesteś zalogowany'; - - @override - String get step_2 => 'Krok 2'; - - @override - String get step_2_steps => '1. Jeśli jesteś zalogowany, naciśnij klawisz F12 lub Kliknij prawym przyciskiem myszy > Zbadaj, aby odtworzyć narzędzia developerskie.\n2. Następnie przejdź do zakładki \"Application\" (Chrome, Edge, Brave etc..) lub zakładki \"Storage\" (Firefox, Palemoon etc..)\n3. Przejdź do sekcji \"Cookies\" a następnie do pod-sekcji \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Krok 3'; - - @override - String get step_3_steps => 'Skopiuj wartość ciasteczka \"sp_dc\"'; - - @override - String get success_emoji => 'Sukces!🥳'; - - @override - String get success_message => 'Udało ci się zalogować! Dobra robota, stary!'; - - @override - String get step_4 => 'Krok 4'; - - @override - String get step_4_steps => 'Wklej skopiowaną wartość \"sp_dc\"'; - @override String get something_went_wrong => 'Coś poszło nie tak 🙁'; @@ -1212,7 +1171,7 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Obliczone na podstawie płatności Spotify za strumień\nw zakresie od \$0.003 do \$0.005. Jest to hipotetyczne\nobliczenie mające na celu pokazanie użytkownikowi, ile\nzapłaciliby artystom, gdyby słuchali ich utworów na Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index 87e065b6..f5c17866 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -379,12 +379,6 @@ class AppLocalizationsPt extends AppLocalizations { @override String get account => 'Conta'; - @override - String get login_with_spotify => 'Fazer login com sua conta do Spotify'; - - @override - String get connect_with_spotify => 'Conectar ao Spotify'; - @override String get logout => 'Sair'; @@ -537,9 +531,6 @@ class AppLocalizationsPt extends AppLocalizations { @override String get license => 'Licença'; - @override - String get add_spotify_credentials => 'Adicione suas credenciais do Spotify para começar'; - @override String get credentials_will_not_be_shared_disclaimer => 'Não se preocupe, suas credenciais não serão coletadas nem compartilhadas com ninguém'; @@ -549,11 +540,6 @@ class AppLocalizationsPt extends AppLocalizations { @override String get follow_step_by_step_guide => 'Siga o guia passo a passo'; - @override - String spotify_cookie(Object name) { - return 'Cookie do Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsPt extends AppLocalizations { @override String get first_go_to => 'Primeiro, vá para'; - @override - String get login_if_not_logged_in => 'e faça login/cadastro se ainda não estiver logado'; - - @override - String get step_2 => 'Passo 2'; - - @override - String get step_2_steps => '1. Uma vez logado, pressione F12 ou clique com o botão direito do mouse > Inspecionar para abrir as ferramentas de desenvolvimento do navegador.\n2. Em seguida, vá para a guia \"Aplicativo\" (Chrome, Edge, Brave, etc.) ou \"Armazenamento\" (Firefox, Palemoon, etc.)\n3. Acesse a seção \"Cookies\" e depois a subseção \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Passo 3'; - - @override - String get step_3_steps => 'Copie o valor do cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Sucesso🥳'; - - @override - String get success_message => 'Agora você está logado com sucesso em sua conta do Spotify. Bom trabalho!'; - - @override - String get step_4 => 'Passo 4'; - - @override - String get step_4_steps => 'Cole o valor copiado de \"sp_dc\"'; - @override String get something_went_wrong => 'Algo deu errado'; @@ -1212,7 +1171,7 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Isso é calculado com base no pagamento por stream do Spotify\nque varia de \$0.003 a \$0.005. Esta é uma cálculo hipotético\npara dar ao usuário uma visão de quanto teriam pago aos artistas\nse eles ouvissem suas músicas no Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index baca93f3..7ee9f50b 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -379,12 +379,6 @@ class AppLocalizationsRu extends AppLocalizations { @override String get account => 'Аккаунт'; - @override - String get login_with_spotify => 'Войдите с помощью своей учетной записи Spotify'; - - @override - String get connect_with_spotify => 'Подключитесь к Spotify'; - @override String get logout => 'Выйти'; @@ -537,9 +531,6 @@ class AppLocalizationsRu extends AppLocalizations { @override String get license => 'Лицензия'; - @override - String get add_spotify_credentials => 'Добавьте ваши учетные данные Spotify, чтобы начать'; - @override String get credentials_will_not_be_shared_disclaimer => 'Не беспокойся, никакая личная информация не собирается и не передается'; @@ -549,11 +540,6 @@ class AppLocalizationsRu extends AppLocalizations { @override String get follow_step_by_step_guide => 'Следуйте пошаговому руководству'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsRu extends AppLocalizations { @override String get first_go_to => 'Сначала перейдите в'; - @override - String get login_if_not_logged_in => 'и войдите или зарегистрируйтесь, если вы не вошли в систему'; - - @override - String get step_2 => 'Шаг 2'; - - @override - String get step_2_steps => '1. После входа в систему нажмите F12 или щелкните правой кнопкой мыши > «Проверить», чтобы открыть инструменты разработчика браузера.\n2. Затем перейдите на вкладку \"Application\" (Chrome, Edge, Brave и т.д..) or \"Storage\" (Firefox, Palemoon и т.д..)\n3. Перейдите в раздел \"Cookies\", а затем в подраздел \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Шаг 3'; - - @override - String get step_3_steps => 'Скопируйте значение Cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Успешно🥳'; - - @override - String get success_message => 'Теперь вы успешно вошли в свою учетную запись Spotify. Отличная работа, приятель!'; - - @override - String get step_4 => 'Шаг 4'; - - @override - String get step_4_steps => 'Вставьте скопированное значение \"sp_dc\"'; - @override String get something_went_wrong => 'Что-то пошло не так'; @@ -1212,7 +1171,7 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Это рассчитано на основе выплат Spotify за стрим\nот \$0.003 до \$0.005. Это гипотетический расчет,\nчтобы дать пользователю представление о том, сколько бы он\nзаплатил артистам, если бы слушал их песни на Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_ta.dart b/lib/l10n/generated/app_localizations_ta.dart index ec29a427..f19c709a 100644 --- a/lib/l10n/generated/app_localizations_ta.dart +++ b/lib/l10n/generated/app_localizations_ta.dart @@ -379,12 +379,6 @@ class AppLocalizationsTa extends AppLocalizations { @override String get account => 'கணக்கு'; - @override - String get login_with_spotify => 'உங்கள் Spotify கணக்கில் உள்நுழைக'; - - @override - String get connect_with_spotify => 'Spotify உடன் இணைக்கவும்'; - @override String get logout => 'வெளியேறு'; @@ -537,9 +531,6 @@ class AppLocalizationsTa extends AppLocalizations { @override String get license => 'உரிமம்'; - @override - String get add_spotify_credentials => 'தொடங்குவதற்கு உங்கள் spotify சான்றுகளைச் சேர்க்கவும்'; - @override String get credentials_will_not_be_shared_disclaimer => 'கவலைப்பட வேண்டாம், உங்கள் சான்றுகள் எதுவும் சேகரிக்கப்படாது அல்லது யாருடனும் பகிரப்படாது'; @@ -549,11 +540,6 @@ class AppLocalizationsTa extends AppLocalizations { @override String get follow_step_by_step_guide => 'படிப்படியான வழிகாட்டியைப் பின்பற்றவும்'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name நட்புநிரல்'; - } - @override String cookie_name_cookie(Object name) { return '$name நட்புநிரல்'; @@ -583,33 +569,6 @@ class AppLocalizationsTa extends AppLocalizations { @override String get first_go_to => 'முதலில், செல்லவேண்டியது'; - @override - String get login_if_not_logged_in => 'நீங்கள் உள்நுழையவில்லை என்றால் உள்நுழைக/பதிவுசெய்க'; - - @override - String get step_2 => 'இரண்டாம் படி'; - - @override - String get step_2_steps => '1. நீங்கள் உள்நுழைந்தவுடன், F12 ஐ அழுத்தவும் அல்லது வலது கிளிக் செய்து > ஆய்வு செய்யவும் உலாவி டெவ்டூல்களைத் திறக்கவும்.\n2. பின்னர் \"பயன்பாடு\" தாவலுக்குச் செல்லவும் (Chrome, Edge, Brave போன்றவை) அல்லது \"சேமிப்பகம்\" தாவல் (Firefox, Palemoon போன்றவை)\n3. \"குக்கிகள்\" பிரிவுக்குச் சென்று பின்னர் \"https://accounts.spotify.com\" பிரிவுக்குச் செல்லவும்'; - - @override - String get step_3 => 'மூன்றாம் படி'; - - @override - String get step_3_steps => '\"sp_dc\" நட்புநிரலின் மதிப்பை நகலெடுக்கவும்'; - - @override - String get success_emoji => 'வெற்றி🥳'; - - @override - String get success_message => 'இப்போது நீங்கள் உங்கள் Spotify கணக்கில் வெற்றிகரமாக உள்நுழைந்துள்ளீர்கள். நல்லது, நண்பரே!'; - - @override - String get step_4 => 'நான்காம் படி'; - - @override - String get step_4_steps => 'நகலெடுக்கப்பட்ட \"sp_dc\" மதிப்பை ஒட்டவும்'; - @override String get something_went_wrong => 'ஏதோ தவறு நடந்துவிட்டது'; @@ -1212,7 +1171,7 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*இது Spotify இன் ஒவ்வொரு ஸ்ட்ரீமிற்கும்\n\$0.003 முதல் \$0.005 வரை அளவீடு அடிப்படையில் கணக்கிடப்படுகிறது. இது ஒரு கற்பனை\nகணக்கீடு ஆகும், பயனர் எந்த அளவிற்கு கலைஞர்களுக்கு\nஅதோர் பாடலை Spotify மென்பொருளில் கேட்டால் எவ்வளவு பணம் செலுத்தினார்கள் என்பதைக் கண்டுபிடிக்க.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index d421fa81..6fba4548 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -379,12 +379,6 @@ class AppLocalizationsTh extends AppLocalizations { @override String get account => 'บัญชี'; - @override - String get login_with_spotify => 'เข้าสู่ระบบด้วยบัญชี Spotify'; - - @override - String get connect_with_spotify => 'เชื่อมต่อกับ Spotify'; - @override String get logout => 'ออกจากระบบ'; @@ -537,9 +531,6 @@ class AppLocalizationsTh extends AppLocalizations { @override String get license => 'ใบอนุญาต'; - @override - String get add_spotify_credentials => 'เพิ่มข้อมูลรับรอง Spotify ของคุณเพื่อเริ่มต้น'; - @override String get credentials_will_not_be_shared_disclaimer => 'ไม่ต้องกังวล ข้อมูลรับรองใดๆ ของคุณจะไม่ถูกเก็บรวบรวมหรือแชร์กับใคร'; @@ -549,11 +540,6 @@ class AppLocalizationsTh extends AppLocalizations { @override String get follow_step_by_step_guide => 'ทำตามคู่มือทีละขั้น'; - @override - String spotify_cookie(Object name) { - return 'คุกกี้ Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'คุกกี้ $name'; @@ -583,33 +569,6 @@ class AppLocalizationsTh extends AppLocalizations { @override String get first_go_to => 'ก่อนอื่น ไปที่'; - @override - String get login_if_not_logged_in => 'ยังไม่ได้เข้าสู่ระบบ ให้เข้าสู่ระบบ/ลงทะเบียน'; - - @override - String get step_2 => 'ขั้นที่ 2'; - - @override - String get step_2_steps => '1. หลังจากเข้าสู่ระบบแล้ว กด F12 หรือ คลิกขวาที่เมาส์ > ตรวจสอบเพื่อเปิด Devtools เบราว์เซอร์\n2. จากนั้นไปที่แท็บ \"แอปพลิเคชัน\" (Chrome, Edge, Brave เป็นต้น) หรือแท็บ \"ที่เก็บข้อมูล\" (Firefox, Palemoon เป็นต้น)\n3. ไปที่ส่วน \"คุกกี้\" แล้วไปที่ subsection \"https: //accounts.spotify.com\"'; - - @override - String get step_3 => 'ขั้นที่ 3'; - - @override - String get step_3_steps => 'คัดลอกค่าคุกกี้ \"sp_dc\"'; - - @override - String get success_emoji => 'สำเร็จ'; - - @override - String get success_message => 'ตอนนี้คุณเข้าสู่ระบบด้วยบัญชี Spotify ของคุณเรียบร้อยแล้ว ยอดเยี่ยม!'; - - @override - String get step_4 => 'ขั้นที่ 4'; - - @override - String get step_4_steps => 'วางค่า \"sp_dc\" ที่คัดลอกมา'; - @override String get something_went_wrong => 'มีอะไรผิดพลาด'; @@ -1212,7 +1171,7 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*คำนวณตามการจ่ายต่อสตรีมของ Spotify\nซึ่งอยู่ในช่วง \$0.003 ถึง \$0.005 นี่เป็นการคำนวณสมมุติ\nเพื่อให้ผู้ใช้ทราบว่าพวกเขาจะจ่ายเงินให้ศิลปินเท่าไหร่\nหากพวกเขาฟังเพลงของพวกเขาใน Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_tl.dart b/lib/l10n/generated/app_localizations_tl.dart index 30a4cdf9..6bbcd8cb 100644 --- a/lib/l10n/generated/app_localizations_tl.dart +++ b/lib/l10n/generated/app_localizations_tl.dart @@ -379,12 +379,6 @@ class AppLocalizationsTl extends AppLocalizations { @override String get account => 'Account'; - @override - String get login_with_spotify => 'Mag-login gamit ang iyong Spotify account'; - - @override - String get connect_with_spotify => 'Kumonekta sa Spotify'; - @override String get logout => 'Mag-logout'; @@ -537,9 +531,6 @@ class AppLocalizationsTl extends AppLocalizations { @override String get license => 'Lisensya'; - @override - String get add_spotify_credentials => 'Idagdag ang iyong mga kredensyal sa spotify para makapagsimula'; - @override String get credentials_will_not_be_shared_disclaimer => 'Huwag mag-alala, ang alinman sa iyong mga kredensyal ay hindi kokolektahin o ibabahagi sa sinuman'; @@ -549,11 +540,6 @@ class AppLocalizationsTl extends AppLocalizations { @override String get follow_step_by_step_guide => 'Sundin ang Hakbang-hakbang na gabay'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsTl extends AppLocalizations { @override String get first_go_to => 'Una, Pumunta sa'; - @override - String get login_if_not_logged_in => 'at Mag-login/Mag-signup kung hindi ka naka-log in'; - - @override - String get step_2 => 'Hakbang 2'; - - @override - String get step_2_steps => '1. Kapag naka-log in ka na, pindutin ang F12 o i-right click ang Mouse > Inspect para Buksan ang Browser devtools.\n2. Pagkatapos ay pumunta sa \"Application\" Tab (Chrome, Edge, Brave atbp..) o \"Storage\" Tab (Firefox, Palemoon atbp..)\n3. Pumunta sa \"Cookies\" na seksyon at pagkatapos sa \"https://accounts.spotify.com\" na subseksyon'; - - @override - String get step_3 => 'Hakbang 3'; - - @override - String get step_3_steps => 'Kopyahin ang halaga ng \"sp_dc\" Cookie'; - - @override - String get success_emoji => 'Tagumpay🥳'; - - @override - String get success_message => 'Ngayon ay matagumpay kang Naka-log in gamit ang iyong Spotify account. Magaling, kaibigan!'; - - @override - String get step_4 => 'Hakbang 4'; - - @override - String get step_4_steps => 'I-paste ang na-kopyang halaga ng \"sp_dc\"'; - @override String get something_went_wrong => 'May nangyaring mali'; @@ -1212,7 +1171,7 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Ito ay kinalkula batay sa bawat stream\nna bayad ng Spotify na \$0.003 hanggang \$0.005. Ito ay isang hypothetical\nna pagkalkula para bigyan ang user ng ideya kung magkano\nang kanilang ibabayad sa mga artista kung sila ay nakikinig\nng kanilang kanta sa Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index 1df5a965..cc275ac3 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -379,12 +379,6 @@ class AppLocalizationsTr extends AppLocalizations { @override String get account => 'Hesap'; - @override - String get login_with_spotify => 'Spotify hesabı ile giriş yap'; - - @override - String get connect_with_spotify => 'Spotify ile bağlan'; - @override String get logout => 'Çıkış yap'; @@ -537,9 +531,6 @@ class AppLocalizationsTr extends AppLocalizations { @override String get license => 'Lisans'; - @override - String get add_spotify_credentials => 'Başlamak için spotify kimlik bilgilerinizi ekleyin'; - @override String get credentials_will_not_be_shared_disclaimer => 'Endişelenmeyin, kimlik bilgilerinizden hiçbiri toplanmayacak veya kimseyle paylaşılmayacak'; @@ -549,11 +540,6 @@ class AppLocalizationsTr extends AppLocalizations { @override String get follow_step_by_step_guide => 'Adım adım kılavuzu takip edin'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name çerezi'; - } - @override String cookie_name_cookie(Object name) { return '$name çerezi'; @@ -583,33 +569,6 @@ class AppLocalizationsTr extends AppLocalizations { @override String get first_go_to => 'İlk olarak şuraya gidin:'; - @override - String get login_if_not_logged_in => 've oturum açmadıysanız Oturum açın/Kaydolun'; - - @override - String get step_2 => '2. Adım'; - - @override - String get step_2_steps => '1. Oturum açtıktan sonra, tarayıcı geliştirme araçlarını açmak için F12\'ye veya fareye sağ tıklayın > İncele\'ye basın.\n2. Daha sonra \"Uygulama\" sekmesine (Chrome, Edge, Brave vb..) veya \"Depolama\" sekmesine (Firefox, Palemoon vb..) gidin\n3. \"Çerezler\" bölümüne, ardından \"https://accounts.spotify.com\" alt bölümüne gidin'; - - @override - String get step_3 => '3. Adım'; - - @override - String get step_3_steps => '\"sp_dc\" Çerezinin değerini kopyalayın'; - - @override - String get success_emoji => 'Başarılı🥳'; - - @override - String get success_message => 'Artık Spotify hesabınızla başarıyla giriş yaptınız. Tebrik ederim!'; - - @override - String get step_4 => '4. Adım'; - - @override - String get step_4_steps => 'Kopyalanan \"sp_dc\" değerini yapıştırın'; - @override String get something_went_wrong => 'Bir hata oluştu'; @@ -1212,7 +1171,7 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Bu, Spotify\'ın her yayın başına ödemenin\n\$0.003 ile \$0.005 arasında olduğu varsayımıyla hesaplanmıştır. Bu\nhipotetik bir hesaplamadır, kullanıcıya şarkılarını Spotify\'da dinlediklerinde\nsanatçılara ne kadar ödeme yapacaklarını gösterir.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_uk.dart b/lib/l10n/generated/app_localizations_uk.dart index 21ffca26..308e80f9 100644 --- a/lib/l10n/generated/app_localizations_uk.dart +++ b/lib/l10n/generated/app_localizations_uk.dart @@ -379,12 +379,6 @@ class AppLocalizationsUk extends AppLocalizations { @override String get account => 'Обліковий запис'; - @override - String get login_with_spotify => 'Увійти за допомогою облікового запису Spotify'; - - @override - String get connect_with_spotify => 'Підключитися до Spotify'; - @override String get logout => 'Вийти'; @@ -537,9 +531,6 @@ class AppLocalizationsUk extends AppLocalizations { @override String get license => 'Ліцензія'; - @override - String get add_spotify_credentials => 'Додайте свої облікові дані Spotify, щоб почати'; - @override String get credentials_will_not_be_shared_disclaimer => 'Не хвилюйтеся, жодні ваші облікові дані не будуть зібрані або передані кому-небудь'; @@ -549,11 +540,6 @@ class AppLocalizationsUk extends AppLocalizations { @override String get follow_step_by_step_guide => 'Дотримуйтесь покрокової інструкції'; - @override - String spotify_cookie(Object name) { - return 'Кукі-файл Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Кукі-файл $name'; @@ -583,33 +569,6 @@ class AppLocalizationsUk extends AppLocalizations { @override String get first_go_to => 'Спочатку перейдіть на'; - @override - String get login_if_not_logged_in => 'та Увійдіть/Зареєструйтесь, якщо ви не ввійшли'; - - @override - String get step_2 => 'Крок 2'; - - @override - String get step_2_steps => '1. Після входу натисніть F12 або клацніть правою кнопкою миші > Інспектувати, щоб відкрити інструменти розробки браузера.\n2. Потім перейдіть на вкладку \'Програма\' (Chrome, Edge, Brave тощо) або вкладку \'Сховище\' (Firefox, Palemoon тощо).\n3. Перейдіть до розділу \'Кукі-файли\', а потім до підрозділу \'https://accounts.spotify.com\''; - - @override - String get step_3 => 'Крок 3'; - - @override - String get step_3_steps => 'Скопіюйте значення cookie \"sp_dc\"'; - - @override - String get success_emoji => 'Успіх🥳'; - - @override - String get success_message => 'Тепер ви успішно ввійшли у свій обліковий запис Spotify. Гарна робота, друже!'; - - @override - String get step_4 => 'Крок 4'; - - @override - String get step_4_steps => 'Вставте скопійоване значення \"sp_dc\"'; - @override String get something_went_wrong => 'Щось пішло не так'; @@ -1212,7 +1171,7 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Це розраховано на основі виплат Spotify за стрім\nвід \$0.003 до \$0.005. Це гіпотетичний розрахунок,\nщоб дати користувачеві уявлення про те, скільки б він заплатив\nартистам, якби слухав їхні пісні на Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_vi.dart b/lib/l10n/generated/app_localizations_vi.dart index 53e5aa7a..5e842438 100644 --- a/lib/l10n/generated/app_localizations_vi.dart +++ b/lib/l10n/generated/app_localizations_vi.dart @@ -379,12 +379,6 @@ class AppLocalizationsVi extends AppLocalizations { @override String get account => 'Tài khoản'; - @override - String get login_with_spotify => 'Đăng nhập bằng tài khoản Spotify của bạn'; - - @override - String get connect_with_spotify => 'Liên kết với Spotify'; - @override String get logout => 'Đăng xuất'; @@ -537,9 +531,6 @@ class AppLocalizationsVi extends AppLocalizations { @override String get license => 'Giấy phép'; - @override - String get add_spotify_credentials => 'Điền thông tin đăng nhập Spotify của bạn'; - @override String get credentials_will_not_be_shared_disclaimer => 'Đừng lo, thông tin đăng nhập của bạn sẽ không được thu thập hoặc chia sẻ với bất kỳ ai'; @@ -549,11 +540,6 @@ class AppLocalizationsVi extends AppLocalizations { @override String get follow_step_by_step_guide => 'Các bước lấy thông tin đăng nhập'; - @override - String spotify_cookie(Object name) { - return 'Cookie Spotify $name'; - } - @override String cookie_name_cookie(Object name) { return 'Cookie $name'; @@ -583,33 +569,6 @@ class AppLocalizationsVi extends AppLocalizations { @override String get first_go_to => 'Đầu tiên, truy cập'; - @override - String get login_if_not_logged_in => 'và Đăng nhập/Đăng ký nếu chưa có tài khoản'; - - @override - String get step_2 => 'Bước 2'; - - @override - String get step_2_steps => '1. Sau khi đăng nhập, nhấn F12 hoặc Chuột phải > Mở devtools của trình duyệt.\n2. Sau đó, chuyển đến Tab \"Ứng dụng/Application\" (Chrome, Edge, Brave, v.v.) hoặc Tab \"Lưu trữ/Storage\" (Firefox, Palemoon, v.v.)\n3. Chuyển đến phần \"Cookie\" sau đó phần con \"https://accounts.spotify.com\"'; - - @override - String get step_3 => 'Bước 3'; - - @override - String get step_3_steps => 'Sao chép giá trị của Cookie \"sp_dc\" và \"sp_key\" (hoặc sp_gaid)'; - - @override - String get success_emoji => 'Thành công🥳'; - - @override - String get success_message => 'Bây giờ bạn đã đăng nhập thành công bằng tài khoản Spotify của mình. Làm tốt lắm!'; - - @override - String get step_4 => 'Bước 4'; - - @override - String get step_4_steps => 'Dán giá trị đã sao chép của Cookie \"sp_dc\" và \"sp_key\" (hoặc sp_gaid) vào các trường tương ứng'; - @override String get something_went_wrong => 'Đã xảy ra lỗi'; @@ -1212,7 +1171,7 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*Được tính toán dựa trên khoản thanh toán của Spotify cho mỗi lượt phát\ntừ \$0.003 đến \$0.005. Đây là một tính toán giả định để\ncung cấp cho người dùng cái nhìn về số tiền họ sẽ phải trả\ncho các nghệ sĩ nếu họ nghe bài hát của họ trên Spotify.'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index eb97a506..d7a6fe84 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -379,12 +379,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get account => '账户'; - @override - String get login_with_spotify => '使用 Spotify 登录'; - - @override - String get connect_with_spotify => '与 Spotify 账户连接'; - @override String get logout => '退出'; @@ -537,9 +531,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get license => '许可证'; - @override - String get add_spotify_credentials => '添加你的 Spotify 登录信息以开始使用'; - @override String get credentials_will_not_be_shared_disclaimer => '不用担心,软件不会收集或分享任何个人数据给第三方'; @@ -549,11 +540,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get follow_step_by_step_guide => '请按照以下指南进行'; - @override - String spotify_cookie(Object name) { - return 'Spotify $name Cookie'; - } - @override String cookie_name_cookie(Object name) { return '$name Cookie'; @@ -583,33 +569,6 @@ class AppLocalizationsZh extends AppLocalizations { @override String get first_go_to => '首先,前往'; - @override - String get login_if_not_logged_in => '如果尚未登录,请登录或者注册一个账户'; - - @override - String get step_2 => '步骤 2'; - - @override - String get step_2_steps => '1. 一旦你已经完成登录, 按 F12 键或者鼠标右击网页空白区域 > 选择“检查”以打开浏览器开发者工具(DevTools)\n2. 然后选择 \"应用(Application)\" 标签页(Chrome, Edge, Brave 等基于 Chromium 的浏览器) 或 \"存储(Storage)\" 标签页 (Firefox, Palemoon 等基于 Firefox 的浏览器))\n3. 选择 \"Cookies\" 栏目然后选择 \"https://accounts.spotify.com\" 子栏目'; - - @override - String get step_3 => '步骤 3'; - - @override - String get step_3_steps => '复制\"sp_dc\" Cookie的值'; - - @override - String get success_emoji => '成功🥳'; - - @override - String get success_message => '你已经成功使用 Spotify 登录。干得漂亮!'; - - @override - String get step_4 => '步骤 4'; - - @override - String get step_4_steps => '粘贴复制的\"sp_dc\"值'; - @override String get something_went_wrong => '某些地方出现了问题'; @@ -1212,7 +1171,7 @@ class AppLocalizationsZh extends AppLocalizations { } @override - String get spotify_hipotetical_calculation => '*根据 Spotify 每次流媒体的支付金额\n\$0.003 到 \$0.005 进行计算。这是一个假设性的\n计算,用于给用户了解他们如果在 Spotify 上\n收听歌曲会支付给艺术家的金额。'; + String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { diff --git a/lib/main.dart b/lib/main.dart index 6a619ad7..67793058 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -16,7 +16,6 @@ import 'package:media_kit/media_kit.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:smtc_windows/smtc_windows.dart'; import 'package:spotube/collections/env.dart'; -import 'package:spotube/collections/initializers.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/hooks/configurators/use_close_behavior.dart'; @@ -66,7 +65,7 @@ Future main(List rawArgs) async { AppLogger.runZoned(() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); - await registerWindowsScheme("spotify"); + // await registerWindowsScheme("spotify"); tz.initializeTimeZones(); diff --git a/lib/models/database/tables/audio_player_state.dart b/lib/models/database/tables/audio_player_state.dart index cf47eb04..bd570da7 100644 --- a/lib/models/database/tables/audio_player_state.dart +++ b/lib/models/database/tables/audio_player_state.dart @@ -18,19 +18,17 @@ class SpotubeTrackObjectListConverter @override List fromSql(String fromDb) { - return fromDb - .split(",") - .where((e) => e.isNotEmpty) - .map( - (e) => SpotubeTrackObject.fromJson( - json.decode(e) as Map, - ), - ) + final raw = (jsonDecode(fromDb) as List).cast(); + + return raw + .map((e) => SpotubeTrackObject.fromJson(e.cast())) .toList(); } @override String toSql(List value) { - return value.map((e) => json.encode(e)).join(","); + return jsonEncode( + value.map((e) => e.toJson()).toList(), + ); } } diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index c058b120..6027c3aa 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -19,7 +19,7 @@ enum ImagePlaceholder { online, } -extension SpotifyImageExtensions on List? { +extension SpotubeImageExtensions on List? { String asUrlString({ int index = 1, required ImagePlaceholder placeholder, diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 65a4d91f..c625a549 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:collection/collection.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; import 'package:metadata_god/metadata_god.dart'; +import 'package:mime/mime.dart'; import 'package:path/path.dart'; import 'package:spotube/collections/assets.gen.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; diff --git a/lib/models/metadata/track.dart b/lib/models/metadata/track.dart index 9520daab..84af429a 100644 --- a/lib/models/metadata/track.dart +++ b/lib/models/metadata/track.dart @@ -84,6 +84,7 @@ extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject { Metadata toMetadata({ required int fileLength, Uint8List? imageBytes, + String? mimeType, }) { return Metadata( title: name, @@ -98,8 +99,9 @@ extension ToMetadataSpotubeFullTrackObject on SpotubeFullTrackObject { picture: imageBytes != null ? Picture( data: imageBytes, - // Spotify images are always JPEGs - mimeType: 'image/jpeg', + mimeType: mimeType ?? + lookupMimeType("", headerBytes: imageBytes) ?? + "image/jpeg", ) : null, ); diff --git a/lib/modules/playlist/playlist_create_dialog.dart b/lib/modules/playlist/playlist_create_dialog.dart index dce17ed0..0fdcf081 100644 --- a/lib/modules/playlist/playlist_create_dialog.dart +++ b/lib/modules/playlist/playlist_create_dialog.dart @@ -70,24 +70,6 @@ class PlaylistCreateDialog extends HookConsumerWidget { }, [playlist]); final onError = useCallback((error) { - // if (error is SpotifyError || error is SpotifyException) { - // showToast( - // context: context, - // location: ToastLocation.topRight, - // builder: (context, overlay) { - // return SurfaceCard( - // child: Basic( - // title: Text( - // l10n.error(error.message ?? l10n.epic_failure), - // style: theme.typography.normal.copyWith( - // color: theme.colorScheme.destructive, - // ), - // ), - // ), - // ); - // }, - // ); - // } showToast( context: context, location: ToastLocation.topRight, diff --git a/lib/pages/getting_started/sections/region.dart b/lib/pages/getting_started/sections/region.dart index b0f3051c..7bfe545d 100644 --- a/lib/pages/getting_started/sections/region.dart +++ b/lib/pages/getting_started/sections/region.dart @@ -1,7 +1,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/language_codes.dart'; -import 'package:spotube/collections/spotify_markets.dart'; +import 'package:spotube/collections/markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/extensions/context.dart'; @@ -14,10 +14,8 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { {super.key, required this.onNext}); bool filterMarkets(dynamic item, String query) { - final market = spotifyMarkets - .firstWhere((element) => element.$1 == item) - .$2 - .toLowerCase(); + final market = + marketsMap.firstWhere((element) => element.$1 == item).$2.toLowerCase(); return market.contains(query.toLowerCase()); } @@ -73,7 +71,7 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { }, placeholder: Text(preferences.market.name), itemBuilder: (context, value) => Text( - spotifyMarkets + marketsMap .firstWhere((element) => element.$1 == value) .$2, ), @@ -82,8 +80,8 @@ class GettingStartedPageLanguageRegionSection extends HookConsumerWidget { builder: (context, searchQuery) { final filteredMarkets = searchQuery == null || searchQuery.isEmpty - ? spotifyMarkets - : spotifyMarkets + ? marketsMap + : marketsMap .where( (element) => filterMarkets(element.$1, searchQuery), diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index a03a8fa4..45d34117 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -29,7 +29,7 @@ class ProfilePage extends HookConsumerWidget { // context.l10n.profile_followers: // meData.followers?.total.toString() ?? "N/A", // context.l10n.birthday: meData.birthdate ?? context.l10n.not_born, - // context.l10n.country: spotifyMarkets + // context.l10n.country: markets // .firstWhere((market) => market.$1 == meData.country) // .$2, // context.l10n.subscription: meData.product ?? context.l10n.hacker, @@ -85,7 +85,7 @@ class ProfilePage extends HookConsumerWidget { leading: const Icon(SpotubeIcons.edit), onPressed: () { launchUrlString( - "https://www.spotify.com/account/profile/", + meData.externalUri, mode: LaunchMode.externalApplication, ); }, diff --git a/lib/pages/settings/sections/language_region.dart b/lib/pages/settings/sections/language_region.dart index 06d9ff01..ba4af41d 100644 --- a/lib/pages/settings/sections/language_region.dart +++ b/lib/pages/settings/sections/language_region.dart @@ -2,7 +2,7 @@ import 'package:collection/collection.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/language_codes.dart'; -import 'package:spotube/collections/spotify_markets.dart'; +import 'package:spotube/collections/markets.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/models/metadata/market.dart'; import 'package:spotube/modules/settings/section_card_with_heading.dart'; @@ -59,7 +59,7 @@ class SettingsLanguageRegionSection extends HookConsumerWidget { if (value == null) return; preferencesNotifier.setRecommendationMarket(value); }, - options: spotifyMarkets + options: marketsMap .map( (country) => SelectItemButton( value: country.$1, diff --git a/lib/pages/stats/fees/fees.dart b/lib/pages/stats/fees/fees.dart index 7419b381..15b93057 100644 --- a/lib/pages/stats/fees/fees.dart +++ b/lib/pages/stats/fees/fees.dart @@ -69,7 +69,7 @@ class StatsStreamFeesPage extends HookConsumerWidget { padding: const EdgeInsets.all(16.0), sliver: SliverToBoxAdapter( child: Text( - context.l10n.spotify_hipotetical_calculation, + context.l10n.hipotetical_calculation, ).small().muted(), ), ), diff --git a/lib/provider/audio_player/audio_player.dart b/lib/provider/audio_player/audio_player.dart index 075a93fe..245c692c 100644 --- a/lib/provider/audio_player/audio_player.dart +++ b/lib/provider/audio_player/audio_player.dart @@ -71,6 +71,10 @@ class AudioPlayerNotifier extends Notifier { ), ); } else if (tracks.isNotEmpty) { + state = state.copyWith( + tracks: tracks, + currentIndex: currentIndex, + ); await audioPlayer.openPlaylist( tracks.asMediaList(), initialIndex: currentIndex, diff --git a/lib/provider/discord_provider.dart b/lib/provider/discord_provider.dart index ee068f10..fb1c41b1 100644 --- a/lib/provider/discord_provider.dart +++ b/lib/provider/discord_provider.dart @@ -86,14 +86,14 @@ class DiscordNotifier extends AsyncNotifier { state: artistNames, assets: RPCAssets( largeImage: - track.album?.images.first.url ?? "spotube-logo-foreground", - largeText: track.album?.name ?? "Unknown album", + track.album.images.firstOrNull?.url ?? "spotube-logo-foreground", + largeText: track.album.name, smallImage: "spotube-logo-foreground", smallText: "Spotube", ), buttons: [ RPCButton( - label: "Listen on Spotify", + label: "Listen on Spotube", url: track.externalUri, ), ], diff --git a/lib/provider/local_tracks/local_tracks_provider.dart b/lib/provider/local_tracks/local_tracks_provider.dart index 3a94815b..89ea6d57 100644 --- a/lib/provider/local_tracks/local_tracks_provider.dart +++ b/lib/provider/local_tracks/local_tracks_provider.dart @@ -44,6 +44,11 @@ final localTracksProvider = final downloadLocation = ref.watch( userPreferencesProvider.select((s) => s.downloadLocation), ); + + if (downloadLocation.isEmpty) { + return {}; + } + final downloadDir = Directory(downloadLocation); final cacheDir = Directory(await UserPreferencesNotifier.getMusicCacheDir()); diff --git a/lib/provider/metadata_plugin/library/tracks.dart b/lib/provider/metadata_plugin/library/tracks.dart index 6ef7a030..d80a8bb1 100644 --- a/lib/provider/metadata_plugin/library/tracks.dart +++ b/lib/provider/metadata_plugin/library/tracks.dart @@ -52,9 +52,10 @@ final metadataPluginSavedTracksProvider = AutoDisposeAsyncNotifierProvider< final metadataPluginIsSavedTrackProvider = FutureProvider.autoDispose.family( (ref, trackId) async { - final metadataPlugin = await ref.watch(metadataPluginProvider.future); + await ref.watch(metadataPluginSavedTracksProvider.future); + final allSavedTracks = + await ref.read(metadataPluginSavedTracksProvider.notifier).fetchAll(); - return metadataPlugin!.user - .isSavedTracks([trackId]).then((value) => value.first); + return allSavedTracks.any((track) => track.id == trackId); }, ); diff --git a/lib/provider/server/routes/playback.dart b/lib/provider/server/routes/playback.dart index eeedc47b..57bb7e7d 100644 --- a/lib/provider/server/routes/playback.dart +++ b/lib/provider/server/routes/playback.dart @@ -7,6 +7,7 @@ import 'package:dio/dio.dart' as dio_lib; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:metadata_god/metadata_god.dart'; +import 'package:mime/mime.dart'; import 'package:path/path.dart'; import 'package:shelf/shelf.dart'; import 'package:spotube/models/metadata/metadata.dart'; @@ -185,7 +186,7 @@ class ServerPlaybackRoutes { } final imageBytes = await ServiceUtils.downloadImage( - (playlistTrack.album?.images).asUrlString( + (playlistTrack.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, index: 1, ), diff --git a/lib/utils/service_utils.dart b/lib/utils/service_utils.dart index b862a83e..3f45901b 100644 --- a/lib/utils/service_utils.dart +++ b/lib/utils/service_utils.dart @@ -390,7 +390,6 @@ abstract class ServiceUtils { } } - /// Spotify Images are always JPEGs static Future downloadImage( String imageUrl, ) async { diff --git a/linux/packaging/appimage/make_config.yaml b/linux/packaging/appimage/make_config.yaml index 94fc5dd9..a877028c 100644 --- a/linux/packaging/appimage/make_config.yaml +++ b/linux/packaging/appimage/make_config.yaml @@ -10,6 +10,5 @@ keywords: generic_name: Music Streaming Application categories: - Music - -supported_mime_type: - - x-scheme-handler/spotify +# supported_mime_type: +# - x-scheme-handler/spotify diff --git a/linux/packaging/deb/make_config.yaml b/linux/packaging/deb/make_config.yaml index 8a7d4672..12e57e1c 100644 --- a/linux/packaging/deb/make_config.yaml +++ b/linux/packaging/deb/make_config.yaml @@ -41,6 +41,5 @@ keywords: generic_name: Music Streaming Application categories: - Music - -supported_mime_type: - - x-scheme-handler/spotify +# supported_mime_type: +# - x-scheme-handler/spotify diff --git a/linux/packaging/rpm/make_config.yaml b/linux/packaging/rpm/make_config.yaml index 57eaae54..e709642e 100644 --- a/linux/packaging/rpm/make_config.yaml +++ b/linux/packaging/rpm/make_config.yaml @@ -32,6 +32,5 @@ categories: - Music startup_notify: true - -supported_mime_type: - - x-scheme-handler/spotify +# supported_mime_type: +# - x-scheme-handler/spotify diff --git a/linux/spotube.desktop b/linux/spotube.desktop index 61fdee50..89b457b3 100644 --- a/linux/spotube.desktop +++ b/linux/spotube.desktop @@ -6,4 +6,4 @@ Icon=/usr/share/icons/spotube/spotube-logo.png Comment=A music streaming app combining the power of music metadata providers & YouTube Terminal=false Categories=Audio;Music;Player;AudioVideo; -MimeType=x-scheme-handler/spotify; \ No newline at end of file +# MimeType=x-scheme-handler/spotify; \ No newline at end of file diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 1a8bb655..450097b5 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -3,18 +3,18 @@ CFBundleURLTypes - - - CFBundleURLName - - Spotify - CFBundleURLSchemes - - - spotify - - - + // + // + // CFBundleURLName + // + // Spotify + // CFBundleURLSchemes + // + // + // spotify + // + // + // CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable diff --git a/pubspec.yaml b/pubspec.yaml index 8ae9ec8b..545c608e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,5 +1,5 @@ name: spotube -description: Open source Spotify client that doesn't require Premium nor uses Electron! Available for both desktop & mobile! +description: Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept publish_to: "none" diff --git a/untranslated_messages.json b/untranslated_messages.json index 7c1927da..5ae99b72 100644 --- a/untranslated_messages.json +++ b/untranslated_messages.json @@ -1,5 +1,114 @@ { + "ar": [ + "hipotetical_calculation" + ], + + "bn": [ + "hipotetical_calculation" + ], + + "ca": [ + "hipotetical_calculation" + ], + + "cs": [ + "hipotetical_calculation" + ], + + "de": [ + "hipotetical_calculation" + ], + + "es": [ + "hipotetical_calculation" + ], + + "eu": [ + "hipotetical_calculation" + ], + + "fa": [ + "hipotetical_calculation" + ], + + "fi": [ + "hipotetical_calculation" + ], + "fr": [ + "hipotetical_calculation", "connection_request_denied" + ], + + "hi": [ + "hipotetical_calculation" + ], + + "id": [ + "hipotetical_calculation" + ], + + "it": [ + "hipotetical_calculation" + ], + + "ja": [ + "hipotetical_calculation" + ], + + "ka": [ + "hipotetical_calculation" + ], + + "ko": [ + "hipotetical_calculation" + ], + + "ne": [ + "hipotetical_calculation" + ], + + "nl": [ + "hipotetical_calculation" + ], + + "pl": [ + "hipotetical_calculation" + ], + + "pt": [ + "hipotetical_calculation" + ], + + "ru": [ + "hipotetical_calculation" + ], + + "ta": [ + "hipotetical_calculation" + ], + + "th": [ + "hipotetical_calculation" + ], + + "tl": [ + "hipotetical_calculation" + ], + + "tr": [ + "hipotetical_calculation" + ], + + "uk": [ + "hipotetical_calculation" + ], + + "vi": [ + "hipotetical_calculation" + ], + + "zh": [ + "hipotetical_calculation" ] } From 1538dc6d520b5d6ff88077e600a1999947e32d46 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 21 Jun 2025 08:33:22 +0600 Subject: [PATCH 29/60] chore: fix macos issues --- macos/Podfile.lock | 10 ++++++++-- macos/Runner/Info.plist | 26 +++++++++++++------------- 2 files changed, 21 insertions(+), 15 deletions(-) diff --git a/macos/Podfile.lock b/macos/Podfile.lock index b3bbbb0b..35483fae 100644 --- a/macos/Podfile.lock +++ b/macos/Podfile.lock @@ -22,7 +22,9 @@ PODS: - flutter_inappwebview_macos (0.0.1): - FlutterMacOS - OrderedSet (~> 6.0.3) - - flutter_secure_storage_macos (6.1.1): + - flutter_secure_storage_macos (6.1.3): + - FlutterMacOS + - flutter_timezone (0.1.0): - FlutterMacOS - FlutterMacOS (1.0.0) - local_notifier (0.1.0): @@ -89,6 +91,7 @@ DEPENDENCIES: - flutter_discord_rpc (from `Flutter/ephemeral/.symlinks/plugins/flutter_discord_rpc/macos`) - flutter_inappwebview_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos`) - flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`) + - flutter_timezone (from `Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos`) - FlutterMacOS (from `Flutter/ephemeral`) - local_notifier (from `Flutter/ephemeral/.symlinks/plugins/local_notifier/macos`) - media_kit_libs_macos_audio (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_audio/macos`) @@ -134,6 +137,8 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/flutter_inappwebview_macos/macos flutter_secure_storage_macos: :path: Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos + flutter_timezone: + :path: Flutter/ephemeral/.symlinks/plugins/flutter_timezone/macos FlutterMacOS: :path: Flutter/ephemeral local_notifier: @@ -178,7 +183,8 @@ SPEC CHECKSUMS: file_selector_macos: 6280b52b459ae6c590af5d78fc35c7267a3c4b31 flutter_discord_rpc: 90614fcca26f3cebfd33263557ea7875936d184b flutter_inappwebview_macos: c2d68649f9f8f1831bfcd98d73fd6256366d9d1d - flutter_secure_storage_macos: b2d62a774c23b060f0b99d0173b0b36abb4a8632 + flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54 + flutter_timezone: d59eea86178cbd7943cd2431cc2eaa9850f935d8 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 local_notifier: ebf072651e35ae5e47280ad52e2707375cb2ae4e media_kit_libs_macos_audio: 06f3cf88d6d89c7c3c87eae57689d1c6adb335b2 diff --git a/macos/Runner/Info.plist b/macos/Runner/Info.plist index 450097b5..374f62dd 100644 --- a/macos/Runner/Info.plist +++ b/macos/Runner/Info.plist @@ -2,19 +2,19 @@ - CFBundleURLTypes - // - // - // CFBundleURLName - // - // Spotify - // CFBundleURLSchemes - // - // - // spotify - // - // - // + + + + + + + + + + + + + CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) CFBundleExecutable From ee71dbf5521b611dceec8be10157eff9f615a3a4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 21 Jun 2025 19:21:00 +0600 Subject: [PATCH 30/60] chore: fix login not working and source quality not good when high quality source not found --- lib/models/database/tables/history.dart | 18 +++--- lib/services/sourced_track/sourced_track.dart | 55 +++++++++++++------ .../sourced_track/sources/youtube.dart | 11 ---- pubspec.lock | 2 +- 4 files changed, 51 insertions(+), 35 deletions(-) diff --git a/lib/models/database/tables/history.dart b/lib/models/database/tables/history.dart index 2faeba9a..f074e248 100644 --- a/lib/models/database/tables/history.dart +++ b/lib/models/database/tables/history.dart @@ -16,12 +16,16 @@ class HistoryTable extends Table { } extension HistoryItemParseExtension on HistoryTableData { - SpotubeSimplePlaylistObject? get playlist => type == HistoryEntryType.playlist - ? SpotubeSimplePlaylistObject.fromJson(data) - : null; - SpotubeSimpleAlbumObject? get album => type == HistoryEntryType.album - ? SpotubeSimpleAlbumObject.fromJson(data) - : null; + SpotubeSimplePlaylistObject? get playlist => + type == HistoryEntryType.playlist && !data.containsKey("external_urls") + ? SpotubeSimplePlaylistObject.fromJson(data) + : null; + SpotubeSimpleAlbumObject? get album => + type == HistoryEntryType.album && !data.containsKey("external_urls") + ? SpotubeSimpleAlbumObject.fromJson(data) + : null; SpotubeTrackObject? get track => - type == HistoryEntryType.track ? SpotubeTrackObject.fromJson(data) : null; + type == HistoryEntryType.track && !data.containsKey("external_urls") + ? SpotubeTrackObject.fromJson(data) + : null; } diff --git a/lib/services/sourced_track/sourced_track.dart b/lib/services/sourced_track/sourced_track.dart index f8caa38d..3e218bad 100644 --- a/lib/services/sourced_track/sourced_track.dart +++ b/lib/services/sourced_track/sourced_track.dart @@ -149,25 +149,48 @@ abstract class SourcedTrack extends BasicSourcedTrack { return getUrlOfCodec(codec); } + /// Returns the URL of the track based on the codec and quality preferences. + /// If an exact match is not found, it will return the closest match based on + /// the user's audio quality preference. + /// + /// If no sources match the codec, it will return the first or last source + /// based on the user's audio quality preference. String getUrlOfCodec(SourceCodecs codec) { final preferences = ref.read(userPreferencesProvider); - return sources - .firstWhereOrNull( - (source) => - source.codec == codec && - source.quality == preferences.audioQuality, - ) - ?.url ?? - // fallback to the first available source of the same codec - sources.firstWhereOrNull((source) => source.codec == codec)?.url ?? - // fallback to the first available source of any codec - sources - .firstWhereOrNull( - (source) => source.quality == preferences.audioQuality) - ?.url ?? - // fallback to the first available source - sources.first.url; + final exactMatch = sources.firstWhereOrNull( + (source) => + source.codec == codec && source.quality == preferences.audioQuality, + ); + + if (exactMatch != null) { + return exactMatch.url; + } + + final sameCodecSources = sources + .where((source) => source.codec == codec) + .toList() + .sorted((a, b) { + final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); + final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); + return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; + }).toList(); + + if (sameCodecSources.isNotEmpty) { + return preferences.audioQuality != SourceQualities.low + ? sameCodecSources.first.url + : sameCodecSources.last.url; + } + + final fallbackSource = sources.sorted((a, b) { + final aDiff = (a.quality.index - preferences.audioQuality.index).abs(); + final bDiff = (b.quality.index - preferences.audioQuality.index).abs(); + return aDiff != bDiff ? aDiff - bDiff : a.quality.index - b.quality.index; + }); + + return preferences.audioQuality != SourceQualities.low + ? fallbackSource.first.url + : fallbackSource.last.url; } SourceCodecs get codec { diff --git a/lib/services/sourced_track/sources/youtube.dart b/lib/services/sourced_track/sources/youtube.dart index 925a48e2..0e494b89 100644 --- a/lib/services/sourced_track/sources/youtube.dart +++ b/lib/services/sourced_track/sources/youtube.dart @@ -95,17 +95,6 @@ class YoutubeSourcedTrack extends SourcedTrack { } static List toTrackSources(StreamManifest manifest) { - var m4a = manifest.audioOnly - .where((audio) => audio.codec.mimeType == "audio/mp4") - .sortByBitrate(); - - var weba = manifest.audioOnly - .where((audio) => audio.codec.mimeType == "audio/webm") - .sortByBitrate(); - - m4a = m4a.isEmpty ? weba.toList() : m4a; - weba = weba.isEmpty ? m4a.toList() : weba; - return manifest.audioOnly.map((streamInfo) { return TrackSource( url: streamInfo.url.toString(), diff --git a/pubspec.lock b/pubspec.lock index 0347baf5..3cc1d962 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1209,7 +1209,7 @@ packages: description: path: "." ref: main - resolved-ref: "1aa924281d2dbe09aab27d8c2de1cffc853b0d16" + resolved-ref: eeb574c3fac0da07ba93c9d972b7eb38960538d2 url: "https://github.com/KRTirtho/hetu_spotube_plugin.git" source: git version: "0.0.1" From cb97f4352c7c1eb01c311445ba4c54be296f4c11 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 21 Jun 2025 21:26:41 +0600 Subject: [PATCH 31/60] chore: fix android build not working --- android/app/proguard-rules.pro | 4 ++-- lib/pages/settings/metadata_plugins.dart | 8 +++----- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 2142555d..1f5a556c 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -1,9 +1,9 @@ #Flutter Wrapper --keep class io.flutter.app.** { *; } +# -keep class io.flutter.app.** { *; } -keep class io.flutter.plugin.** { *; } -keep class io.flutter.util.** { *; } -keep class io.flutter.view.** { *; } --keep class io.flutter.** { *; } +# -keep class io.flutter.** { *; } -keep class io.flutter.plugins.** { *; } -keep class de.prosiebensat1digital.** { *; } diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index a6a30a7f..2de1c36c 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -11,7 +11,7 @@ import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:file_picker/file_picker.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/utils/platform.dart'; @RoutePage() class SettingsMetadataProviderPage extends HookConsumerWidget { @@ -26,8 +26,6 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final metadataPlugin = ref.watch(metadataPluginProvider); final isAuthenticated = ref.watch(metadataPluginAuthenticatedProvider); - final user = ref.watch(metadataPluginUserProvider); - return Scaffold( headers: const [ TitleBar( @@ -85,8 +83,8 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { icon: const Icon(SpotubeIcons.upload), onPressed: () async { final result = await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ["smplug"], + type: kIsAndroid ? FileType.any : FileType.custom, + allowedExtensions: kIsAndroid ? [] : ["smplug"], withData: true, ); From 5b457fc4bdf2c6e362eea0ab0564f416f87f39d9 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 21 Jun 2025 22:40:20 +0600 Subject: [PATCH 32/60] chore: fix streaming quality is worse then available --- .../youtube_engine/newpipe_engine.dart | 6 ++++- .../youtube_explode_engine.dart | 27 +++++++++++++++++-- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/lib/services/youtube_engine/newpipe_engine.dart b/lib/services/youtube_engine/newpipe_engine.dart index f58fc333..f4a87f33 100644 --- a/lib/services/youtube_engine/newpipe_engine.dart +++ b/lib/services/youtube_engine/newpipe_engine.dart @@ -17,7 +17,11 @@ class NewPipeEngine implements YouTubeEngine { FileSize.unknown, Bitrate(stream.bitrate), stream.codec, - stream.quality, + switch (stream.bitrate) { + > 130 * 1024 => "high", + > 64 * 1024 => "medium", + _ => "low", + }, [], MediaType.parse(stream.mediaFormat!.mimeType), null, diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index 10a4780b..15906aad 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -11,8 +11,9 @@ class YouTubeExplodeEngine implements YouTubeEngine { } @override - Future getStreamManifest(String videoId) { - return _youtubeExplode.videos.streamsClient.getManifest( + Future getStreamManifest(String videoId) async { + final streamManifest = + await _youtubeExplode.videos.streamsClient.getManifest( videoId, requireWatchPage: false, ytClients: [ @@ -21,6 +22,28 @@ class YouTubeExplodeEngine implements YouTubeEngine { YoutubeApiClient.mweb, ], ); + + return StreamManifest( + streamManifest.audioOnly.map((stream) { + return AudioOnlyStreamInfo( + stream.videoId, + stream.tag, + stream.url, + stream.container, + stream.size, + stream.bitrate, + stream.audioCodec, + switch (stream.bitrate.bitsPerSecond) { + > 130 * 1024 => "high", + > 64 * 1024 => "medium", + _ => "low", + }, + stream.fragments, + stream.codec, + stream.audioTrack, + ); + }), + ); } @override From 5be4747c66fedf4e35cd8f93330587bcd17437f7 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 3 Jul 2025 23:55:29 +0600 Subject: [PATCH 33/60] feat(metadata): add plugin form --- lib/collections/routes.dart | 4 + lib/collections/routes.gr.dart | 432 +++++++------ lib/models/metadata/fields.dart | 26 + lib/models/metadata/metadata.dart | 1 + lib/models/metadata/metadata.freezed.dart | 603 ++++++++++++++++++ lib/models/metadata/metadata.g.dart | 46 ++ .../settings/metadata/metadata_form.dart | 141 ++++ lib/services/metadata/metadata.dart | 15 + pubspec.lock | 18 +- pubspec.yaml | 1 + 10 files changed, 1097 insertions(+), 190 deletions(-) create mode 100644 lib/models/metadata/fields.dart create mode 100644 lib/pages/settings/metadata/metadata_form.dart diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 19a63587..184a051b 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -91,6 +91,10 @@ class AppRouter extends RootStackRouter { path: "settings/metadata-provider", page: SettingsMetadataProviderRoute.page, ), + AutoRoute( + path: "settings/metadata-provider/metadata-form", + page: SettingsMetadataProviderFormRoute.page, + ), AutoRoute( path: "settings/blacklist", page: BlackListRoute.page, diff --git a/lib/collections/routes.gr.dart b/lib/collections/routes.gr.dart index d998e45a..a5138c2f 100644 --- a/lib/collections/routes.gr.dart +++ b/lib/collections/routes.gr.dart @@ -8,10 +8,10 @@ // coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'package:auto_route/auto_route.dart' as _i39; -import 'package:flutter/material.dart' as _i40; -import 'package:shadcn_flutter/shadcn_flutter.dart' as _i42; -import 'package:spotube/models/metadata/metadata.dart' as _i41; +import 'package:auto_route/auto_route.dart' as _i40; +import 'package:flutter/material.dart' as _i41; +import 'package:shadcn_flutter/shadcn_flutter.dart' as _i43; +import 'package:spotube/models/metadata/metadata.dart' as _i42; import 'package:spotube/pages/album/album.dart' as _i2; import 'package:spotube/pages/artist/artist.dart' as _i3; import 'package:spotube/pages/connect/connect.dart' as _i6; @@ -21,14 +21,14 @@ import 'package:spotube/pages/home/home.dart' as _i9; import 'package:spotube/pages/home/sections/section_items.dart' as _i8; import 'package:spotube/pages/lastfm_login/lastfm_login.dart' as _i10; import 'package:spotube/pages/library/library.dart' as _i11; -import 'package:spotube/pages/library/user_albums.dart' as _i34; -import 'package:spotube/pages/library/user_artists.dart' as _i35; -import 'package:spotube/pages/library/user_downloads.dart' as _i36; +import 'package:spotube/pages/library/user_albums.dart' as _i35; +import 'package:spotube/pages/library/user_artists.dart' as _i36; +import 'package:spotube/pages/library/user_downloads.dart' as _i37; import 'package:spotube/pages/library/user_local_tracks/local_folder.dart' as _i13; import 'package:spotube/pages/library/user_local_tracks/user_local_tracks.dart' - as _i37; -import 'package:spotube/pages/library/user_playlists.dart' as _i38; + as _i38; +import 'package:spotube/pages/library/user_playlists.dart' as _i39; import 'package:spotube/pages/lyrics/lyrics.dart' as _i15; import 'package:spotube/pages/lyrics/mini_lyrics.dart' as _i16; import 'package:spotube/pages/player/lyrics.dart' as _i17; @@ -42,21 +42,22 @@ import 'package:spotube/pages/search/search.dart' as _i23; import 'package:spotube/pages/settings/about.dart' as _i1; import 'package:spotube/pages/settings/blacklist.dart' as _i4; import 'package:spotube/pages/settings/logs.dart' as _i14; -import 'package:spotube/pages/settings/metadata_plugins.dart' as _i24; -import 'package:spotube/pages/settings/settings.dart' as _i25; -import 'package:spotube/pages/stats/albums/albums.dart' as _i26; -import 'package:spotube/pages/stats/artists/artists.dart' as _i27; -import 'package:spotube/pages/stats/fees/fees.dart' as _i31; -import 'package:spotube/pages/stats/minutes/minutes.dart' as _i28; -import 'package:spotube/pages/stats/playlists/playlists.dart' as _i30; -import 'package:spotube/pages/stats/stats.dart' as _i29; -import 'package:spotube/pages/stats/streams/streams.dart' as _i32; -import 'package:spotube/pages/track/track.dart' as _i33; +import 'package:spotube/pages/settings/metadata/metadata_form.dart' as _i24; +import 'package:spotube/pages/settings/metadata_plugins.dart' as _i25; +import 'package:spotube/pages/settings/settings.dart' as _i26; +import 'package:spotube/pages/stats/albums/albums.dart' as _i27; +import 'package:spotube/pages/stats/artists/artists.dart' as _i28; +import 'package:spotube/pages/stats/fees/fees.dart' as _i32; +import 'package:spotube/pages/stats/minutes/minutes.dart' as _i29; +import 'package:spotube/pages/stats/playlists/playlists.dart' as _i31; +import 'package:spotube/pages/stats/stats.dart' as _i30; +import 'package:spotube/pages/stats/streams/streams.dart' as _i33; +import 'package:spotube/pages/track/track.dart' as _i34; /// generated route for /// [_i1.AboutSpotubePage] -class AboutSpotubeRoute extends _i39.PageRouteInfo { - const AboutSpotubeRoute({List<_i39.PageRouteInfo>? children}) +class AboutSpotubeRoute extends _i40.PageRouteInfo { + const AboutSpotubeRoute({List<_i40.PageRouteInfo>? children}) : super( AboutSpotubeRoute.name, initialChildren: children, @@ -64,7 +65,7 @@ class AboutSpotubeRoute extends _i39.PageRouteInfo { static const String name = 'AboutSpotubeRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i1.AboutSpotubePage(); @@ -74,12 +75,12 @@ class AboutSpotubeRoute extends _i39.PageRouteInfo { /// generated route for /// [_i2.AlbumPage] -class AlbumRoute extends _i39.PageRouteInfo { +class AlbumRoute extends _i40.PageRouteInfo { AlbumRoute({ - _i40.Key? key, + _i41.Key? key, required String id, - required _i41.SpotubeSimpleAlbumObject album, - List<_i39.PageRouteInfo>? children, + required _i42.SpotubeSimpleAlbumObject album, + List<_i40.PageRouteInfo>? children, }) : super( AlbumRoute.name, args: AlbumRouteArgs( @@ -93,7 +94,7 @@ class AlbumRoute extends _i39.PageRouteInfo { static const String name = 'AlbumRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -113,11 +114,11 @@ class AlbumRouteArgs { required this.album, }); - final _i40.Key? key; + final _i41.Key? key; final String id; - final _i41.SpotubeSimpleAlbumObject album; + final _i42.SpotubeSimpleAlbumObject album; @override String toString() { @@ -127,11 +128,11 @@ class AlbumRouteArgs { /// generated route for /// [_i3.ArtistPage] -class ArtistRoute extends _i39.PageRouteInfo { +class ArtistRoute extends _i40.PageRouteInfo { ArtistRoute({ required String artistId, - _i40.Key? key, - List<_i39.PageRouteInfo>? children, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( ArtistRoute.name, args: ArtistRouteArgs( @@ -144,7 +145,7 @@ class ArtistRoute extends _i39.PageRouteInfo { static const String name = 'ArtistRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; @@ -166,7 +167,7 @@ class ArtistRouteArgs { final String artistId; - final _i40.Key? key; + final _i41.Key? key; @override String toString() { @@ -176,8 +177,8 @@ class ArtistRouteArgs { /// generated route for /// [_i4.BlackListPage] -class BlackListRoute extends _i39.PageRouteInfo { - const BlackListRoute({List<_i39.PageRouteInfo>? children}) +class BlackListRoute extends _i40.PageRouteInfo { + const BlackListRoute({List<_i40.PageRouteInfo>? children}) : super( BlackListRoute.name, initialChildren: children, @@ -185,7 +186,7 @@ class BlackListRoute extends _i39.PageRouteInfo { static const String name = 'BlackListRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i4.BlackListPage(); @@ -195,8 +196,8 @@ class BlackListRoute extends _i39.PageRouteInfo { /// generated route for /// [_i5.ConnectControlPage] -class ConnectControlRoute extends _i39.PageRouteInfo { - const ConnectControlRoute({List<_i39.PageRouteInfo>? children}) +class ConnectControlRoute extends _i40.PageRouteInfo { + const ConnectControlRoute({List<_i40.PageRouteInfo>? children}) : super( ConnectControlRoute.name, initialChildren: children, @@ -204,7 +205,7 @@ class ConnectControlRoute extends _i39.PageRouteInfo { static const String name = 'ConnectControlRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i5.ConnectControlPage(); @@ -214,8 +215,8 @@ class ConnectControlRoute extends _i39.PageRouteInfo { /// generated route for /// [_i6.ConnectPage] -class ConnectRoute extends _i39.PageRouteInfo { - const ConnectRoute({List<_i39.PageRouteInfo>? children}) +class ConnectRoute extends _i40.PageRouteInfo { + const ConnectRoute({List<_i40.PageRouteInfo>? children}) : super( ConnectRoute.name, initialChildren: children, @@ -223,7 +224,7 @@ class ConnectRoute extends _i39.PageRouteInfo { static const String name = 'ConnectRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i6.ConnectPage(); @@ -233,8 +234,8 @@ class ConnectRoute extends _i39.PageRouteInfo { /// generated route for /// [_i7.GettingStartedPage] -class GettingStartedRoute extends _i39.PageRouteInfo { - const GettingStartedRoute({List<_i39.PageRouteInfo>? children}) +class GettingStartedRoute extends _i40.PageRouteInfo { + const GettingStartedRoute({List<_i40.PageRouteInfo>? children}) : super( GettingStartedRoute.name, initialChildren: children, @@ -242,7 +243,7 @@ class GettingStartedRoute extends _i39.PageRouteInfo { static const String name = 'GettingStartedRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i7.GettingStartedPage(); @@ -253,12 +254,12 @@ class GettingStartedRoute extends _i39.PageRouteInfo { /// generated route for /// [_i8.HomeBrowseSectionItemsPage] class HomeBrowseSectionItemsRoute - extends _i39.PageRouteInfo { + extends _i40.PageRouteInfo { HomeBrowseSectionItemsRoute({ - _i42.Key? key, + _i43.Key? key, required String sectionId, - required _i41.SpotubeBrowseSectionObject section, - List<_i39.PageRouteInfo>? children, + required _i42.SpotubeBrowseSectionObject section, + List<_i40.PageRouteInfo>? children, }) : super( HomeBrowseSectionItemsRoute.name, args: HomeBrowseSectionItemsRouteArgs( @@ -272,7 +273,7 @@ class HomeBrowseSectionItemsRoute static const String name = 'HomeBrowseSectionItemsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -292,11 +293,11 @@ class HomeBrowseSectionItemsRouteArgs { required this.section, }); - final _i42.Key? key; + final _i43.Key? key; final String sectionId; - final _i41.SpotubeBrowseSectionObject section; + final _i42.SpotubeBrowseSectionObject section; @override String toString() { @@ -306,8 +307,8 @@ class HomeBrowseSectionItemsRouteArgs { /// generated route for /// [_i9.HomePage] -class HomeRoute extends _i39.PageRouteInfo { - const HomeRoute({List<_i39.PageRouteInfo>? children}) +class HomeRoute extends _i40.PageRouteInfo { + const HomeRoute({List<_i40.PageRouteInfo>? children}) : super( HomeRoute.name, initialChildren: children, @@ -315,7 +316,7 @@ class HomeRoute extends _i39.PageRouteInfo { static const String name = 'HomeRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i9.HomePage(); @@ -325,8 +326,8 @@ class HomeRoute extends _i39.PageRouteInfo { /// generated route for /// [_i10.LastFMLoginPage] -class LastFMLoginRoute extends _i39.PageRouteInfo { - const LastFMLoginRoute({List<_i39.PageRouteInfo>? children}) +class LastFMLoginRoute extends _i40.PageRouteInfo { + const LastFMLoginRoute({List<_i40.PageRouteInfo>? children}) : super( LastFMLoginRoute.name, initialChildren: children, @@ -334,7 +335,7 @@ class LastFMLoginRoute extends _i39.PageRouteInfo { static const String name = 'LastFMLoginRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i10.LastFMLoginPage(); @@ -344,8 +345,8 @@ class LastFMLoginRoute extends _i39.PageRouteInfo { /// generated route for /// [_i11.LibraryPage] -class LibraryRoute extends _i39.PageRouteInfo { - const LibraryRoute({List<_i39.PageRouteInfo>? children}) +class LibraryRoute extends _i40.PageRouteInfo { + const LibraryRoute({List<_i40.PageRouteInfo>? children}) : super( LibraryRoute.name, initialChildren: children, @@ -353,7 +354,7 @@ class LibraryRoute extends _i39.PageRouteInfo { static const String name = 'LibraryRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i11.LibraryPage(); @@ -363,11 +364,11 @@ class LibraryRoute extends _i39.PageRouteInfo { /// generated route for /// [_i12.LikedPlaylistPage] -class LikedPlaylistRoute extends _i39.PageRouteInfo { +class LikedPlaylistRoute extends _i40.PageRouteInfo { LikedPlaylistRoute({ - _i40.Key? key, - required _i41.SpotubeSimplePlaylistObject playlist, - List<_i39.PageRouteInfo>? children, + _i41.Key? key, + required _i42.SpotubeSimplePlaylistObject playlist, + List<_i40.PageRouteInfo>? children, }) : super( LikedPlaylistRoute.name, args: LikedPlaylistRouteArgs( @@ -379,7 +380,7 @@ class LikedPlaylistRoute extends _i39.PageRouteInfo { static const String name = 'LikedPlaylistRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -397,9 +398,9 @@ class LikedPlaylistRouteArgs { required this.playlist, }); - final _i40.Key? key; + final _i41.Key? key; - final _i41.SpotubeSimplePlaylistObject playlist; + final _i42.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -409,13 +410,13 @@ class LikedPlaylistRouteArgs { /// generated route for /// [_i13.LocalLibraryPage] -class LocalLibraryRoute extends _i39.PageRouteInfo { +class LocalLibraryRoute extends _i40.PageRouteInfo { LocalLibraryRoute({ required String location, - _i40.Key? key, + _i41.Key? key, bool isDownloads = false, bool isCache = false, - List<_i39.PageRouteInfo>? children, + List<_i40.PageRouteInfo>? children, }) : super( LocalLibraryRoute.name, args: LocalLibraryRouteArgs( @@ -429,7 +430,7 @@ class LocalLibraryRoute extends _i39.PageRouteInfo { static const String name = 'LocalLibraryRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -453,7 +454,7 @@ class LocalLibraryRouteArgs { final String location; - final _i40.Key? key; + final _i41.Key? key; final bool isDownloads; @@ -467,8 +468,8 @@ class LocalLibraryRouteArgs { /// generated route for /// [_i14.LogsPage] -class LogsRoute extends _i39.PageRouteInfo { - const LogsRoute({List<_i39.PageRouteInfo>? children}) +class LogsRoute extends _i40.PageRouteInfo { + const LogsRoute({List<_i40.PageRouteInfo>? children}) : super( LogsRoute.name, initialChildren: children, @@ -476,7 +477,7 @@ class LogsRoute extends _i39.PageRouteInfo { static const String name = 'LogsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i14.LogsPage(); @@ -486,8 +487,8 @@ class LogsRoute extends _i39.PageRouteInfo { /// generated route for /// [_i15.LyricsPage] -class LyricsRoute extends _i39.PageRouteInfo { - const LyricsRoute({List<_i39.PageRouteInfo>? children}) +class LyricsRoute extends _i40.PageRouteInfo { + const LyricsRoute({List<_i40.PageRouteInfo>? children}) : super( LyricsRoute.name, initialChildren: children, @@ -495,7 +496,7 @@ class LyricsRoute extends _i39.PageRouteInfo { static const String name = 'LyricsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i15.LyricsPage(); @@ -505,11 +506,11 @@ class LyricsRoute extends _i39.PageRouteInfo { /// generated route for /// [_i16.MiniLyricsPage] -class MiniLyricsRoute extends _i39.PageRouteInfo { +class MiniLyricsRoute extends _i40.PageRouteInfo { MiniLyricsRoute({ - _i42.Key? key, - required _i42.Size prevSize, - List<_i39.PageRouteInfo>? children, + _i43.Key? key, + required _i43.Size prevSize, + List<_i40.PageRouteInfo>? children, }) : super( MiniLyricsRoute.name, args: MiniLyricsRouteArgs( @@ -521,7 +522,7 @@ class MiniLyricsRoute extends _i39.PageRouteInfo { static const String name = 'MiniLyricsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -539,9 +540,9 @@ class MiniLyricsRouteArgs { required this.prevSize, }); - final _i42.Key? key; + final _i43.Key? key; - final _i42.Size prevSize; + final _i43.Size prevSize; @override String toString() { @@ -551,8 +552,8 @@ class MiniLyricsRouteArgs { /// generated route for /// [_i17.PlayerLyricsPage] -class PlayerLyricsRoute extends _i39.PageRouteInfo { - const PlayerLyricsRoute({List<_i39.PageRouteInfo>? children}) +class PlayerLyricsRoute extends _i40.PageRouteInfo { + const PlayerLyricsRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerLyricsRoute.name, initialChildren: children, @@ -560,7 +561,7 @@ class PlayerLyricsRoute extends _i39.PageRouteInfo { static const String name = 'PlayerLyricsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i17.PlayerLyricsPage(); @@ -570,8 +571,8 @@ class PlayerLyricsRoute extends _i39.PageRouteInfo { /// generated route for /// [_i18.PlayerQueuePage] -class PlayerQueueRoute extends _i39.PageRouteInfo { - const PlayerQueueRoute({List<_i39.PageRouteInfo>? children}) +class PlayerQueueRoute extends _i40.PageRouteInfo { + const PlayerQueueRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerQueueRoute.name, initialChildren: children, @@ -579,7 +580,7 @@ class PlayerQueueRoute extends _i39.PageRouteInfo { static const String name = 'PlayerQueueRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i18.PlayerQueuePage(); @@ -589,8 +590,8 @@ class PlayerQueueRoute extends _i39.PageRouteInfo { /// generated route for /// [_i19.PlayerTrackSourcesPage] -class PlayerTrackSourcesRoute extends _i39.PageRouteInfo { - const PlayerTrackSourcesRoute({List<_i39.PageRouteInfo>? children}) +class PlayerTrackSourcesRoute extends _i40.PageRouteInfo { + const PlayerTrackSourcesRoute({List<_i40.PageRouteInfo>? children}) : super( PlayerTrackSourcesRoute.name, initialChildren: children, @@ -598,7 +599,7 @@ class PlayerTrackSourcesRoute extends _i39.PageRouteInfo { static const String name = 'PlayerTrackSourcesRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i19.PlayerTrackSourcesPage(); @@ -608,12 +609,12 @@ class PlayerTrackSourcesRoute extends _i39.PageRouteInfo { /// generated route for /// [_i20.PlaylistPage] -class PlaylistRoute extends _i39.PageRouteInfo { +class PlaylistRoute extends _i40.PageRouteInfo { PlaylistRoute({ - _i40.Key? key, + _i41.Key? key, required String id, - required _i41.SpotubeSimplePlaylistObject playlist, - List<_i39.PageRouteInfo>? children, + required _i42.SpotubeSimplePlaylistObject playlist, + List<_i40.PageRouteInfo>? children, }) : super( PlaylistRoute.name, args: PlaylistRouteArgs( @@ -627,7 +628,7 @@ class PlaylistRoute extends _i39.PageRouteInfo { static const String name = 'PlaylistRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final args = data.argsAs(); @@ -647,11 +648,11 @@ class PlaylistRouteArgs { required this.playlist, }); - final _i40.Key? key; + final _i41.Key? key; final String id; - final _i41.SpotubeSimplePlaylistObject playlist; + final _i42.SpotubeSimplePlaylistObject playlist; @override String toString() { @@ -661,8 +662,8 @@ class PlaylistRouteArgs { /// generated route for /// [_i21.ProfilePage] -class ProfileRoute extends _i39.PageRouteInfo { - const ProfileRoute({List<_i39.PageRouteInfo>? children}) +class ProfileRoute extends _i40.PageRouteInfo { + const ProfileRoute({List<_i40.PageRouteInfo>? children}) : super( ProfileRoute.name, initialChildren: children, @@ -670,7 +671,7 @@ class ProfileRoute extends _i39.PageRouteInfo { static const String name = 'ProfileRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i21.ProfilePage(); @@ -680,8 +681,8 @@ class ProfileRoute extends _i39.PageRouteInfo { /// generated route for /// [_i22.RootAppPage] -class RootAppRoute extends _i39.PageRouteInfo { - const RootAppRoute({List<_i39.PageRouteInfo>? children}) +class RootAppRoute extends _i40.PageRouteInfo { + const RootAppRoute({List<_i40.PageRouteInfo>? children}) : super( RootAppRoute.name, initialChildren: children, @@ -689,7 +690,7 @@ class RootAppRoute extends _i39.PageRouteInfo { static const String name = 'RootAppRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i22.RootAppPage(); @@ -699,8 +700,8 @@ class RootAppRoute extends _i39.PageRouteInfo { /// generated route for /// [_i23.SearchPage] -class SearchRoute extends _i39.PageRouteInfo { - const SearchRoute({List<_i39.PageRouteInfo>? children}) +class SearchRoute extends _i40.PageRouteInfo { + const SearchRoute({List<_i40.PageRouteInfo>? children}) : super( SearchRoute.name, initialChildren: children, @@ -708,7 +709,7 @@ class SearchRoute extends _i39.PageRouteInfo { static const String name = 'SearchRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { return const _i23.SearchPage(); @@ -717,9 +718,62 @@ class SearchRoute extends _i39.PageRouteInfo { } /// generated route for -/// [_i24.SettingsMetadataProviderPage] -class SettingsMetadataProviderRoute extends _i39.PageRouteInfo { - const SettingsMetadataProviderRoute({List<_i39.PageRouteInfo>? children}) +/// [_i24.SettingsMetadataProviderFormPage] +class SettingsMetadataProviderFormRoute + extends _i40.PageRouteInfo { + SettingsMetadataProviderFormRoute({ + _i43.Key? key, + required String title, + required List<_i42.MetadataFormFieldObject> fields, + List<_i40.PageRouteInfo>? children, + }) : super( + SettingsMetadataProviderFormRoute.name, + args: SettingsMetadataProviderFormRouteArgs( + key: key, + title: title, + fields: fields, + ), + initialChildren: children, + ); + + static const String name = 'SettingsMetadataProviderFormRoute'; + + static _i40.PageInfo page = _i40.PageInfo( + name, + builder: (data) { + final args = data.argsAs(); + return _i24.SettingsMetadataProviderFormPage( + key: args.key, + title: args.title, + fields: args.fields, + ); + }, + ); +} + +class SettingsMetadataProviderFormRouteArgs { + const SettingsMetadataProviderFormRouteArgs({ + this.key, + required this.title, + required this.fields, + }); + + final _i43.Key? key; + + final String title; + + final List<_i42.MetadataFormFieldObject> fields; + + @override + String toString() { + return 'SettingsMetadataProviderFormRouteArgs{key: $key, title: $title, fields: $fields}'; + } +} + +/// generated route for +/// [_i25.SettingsMetadataProviderPage] +class SettingsMetadataProviderRoute extends _i40.PageRouteInfo { + const SettingsMetadataProviderRoute({List<_i40.PageRouteInfo>? children}) : super( SettingsMetadataProviderRoute.name, initialChildren: children, @@ -727,18 +781,18 @@ class SettingsMetadataProviderRoute extends _i39.PageRouteInfo { static const String name = 'SettingsMetadataProviderRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i24.SettingsMetadataProviderPage(); + return const _i25.SettingsMetadataProviderPage(); }, ); } /// generated route for -/// [_i25.SettingsPage] -class SettingsRoute extends _i39.PageRouteInfo { - const SettingsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i26.SettingsPage] +class SettingsRoute extends _i40.PageRouteInfo { + const SettingsRoute({List<_i40.PageRouteInfo>? children}) : super( SettingsRoute.name, initialChildren: children, @@ -746,18 +800,18 @@ class SettingsRoute extends _i39.PageRouteInfo { static const String name = 'SettingsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i25.SettingsPage(); + return const _i26.SettingsPage(); }, ); } /// generated route for -/// [_i26.StatsAlbumsPage] -class StatsAlbumsRoute extends _i39.PageRouteInfo { - const StatsAlbumsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i27.StatsAlbumsPage] +class StatsAlbumsRoute extends _i40.PageRouteInfo { + const StatsAlbumsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsAlbumsRoute.name, initialChildren: children, @@ -765,18 +819,18 @@ class StatsAlbumsRoute extends _i39.PageRouteInfo { static const String name = 'StatsAlbumsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i26.StatsAlbumsPage(); + return const _i27.StatsAlbumsPage(); }, ); } /// generated route for -/// [_i27.StatsArtistsPage] -class StatsArtistsRoute extends _i39.PageRouteInfo { - const StatsArtistsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i28.StatsArtistsPage] +class StatsArtistsRoute extends _i40.PageRouteInfo { + const StatsArtistsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsArtistsRoute.name, initialChildren: children, @@ -784,18 +838,18 @@ class StatsArtistsRoute extends _i39.PageRouteInfo { static const String name = 'StatsArtistsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i27.StatsArtistsPage(); + return const _i28.StatsArtistsPage(); }, ); } /// generated route for -/// [_i28.StatsMinutesPage] -class StatsMinutesRoute extends _i39.PageRouteInfo { - const StatsMinutesRoute({List<_i39.PageRouteInfo>? children}) +/// [_i29.StatsMinutesPage] +class StatsMinutesRoute extends _i40.PageRouteInfo { + const StatsMinutesRoute({List<_i40.PageRouteInfo>? children}) : super( StatsMinutesRoute.name, initialChildren: children, @@ -803,18 +857,18 @@ class StatsMinutesRoute extends _i39.PageRouteInfo { static const String name = 'StatsMinutesRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i28.StatsMinutesPage(); + return const _i29.StatsMinutesPage(); }, ); } /// generated route for -/// [_i29.StatsPage] -class StatsRoute extends _i39.PageRouteInfo { - const StatsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i30.StatsPage] +class StatsRoute extends _i40.PageRouteInfo { + const StatsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsRoute.name, initialChildren: children, @@ -822,18 +876,18 @@ class StatsRoute extends _i39.PageRouteInfo { static const String name = 'StatsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i29.StatsPage(); + return const _i30.StatsPage(); }, ); } /// generated route for -/// [_i30.StatsPlaylistsPage] -class StatsPlaylistsRoute extends _i39.PageRouteInfo { - const StatsPlaylistsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i31.StatsPlaylistsPage] +class StatsPlaylistsRoute extends _i40.PageRouteInfo { + const StatsPlaylistsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsPlaylistsRoute.name, initialChildren: children, @@ -841,18 +895,18 @@ class StatsPlaylistsRoute extends _i39.PageRouteInfo { static const String name = 'StatsPlaylistsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i30.StatsPlaylistsPage(); + return const _i31.StatsPlaylistsPage(); }, ); } /// generated route for -/// [_i31.StatsStreamFeesPage] -class StatsStreamFeesRoute extends _i39.PageRouteInfo { - const StatsStreamFeesRoute({List<_i39.PageRouteInfo>? children}) +/// [_i32.StatsStreamFeesPage] +class StatsStreamFeesRoute extends _i40.PageRouteInfo { + const StatsStreamFeesRoute({List<_i40.PageRouteInfo>? children}) : super( StatsStreamFeesRoute.name, initialChildren: children, @@ -860,18 +914,18 @@ class StatsStreamFeesRoute extends _i39.PageRouteInfo { static const String name = 'StatsStreamFeesRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i31.StatsStreamFeesPage(); + return const _i32.StatsStreamFeesPage(); }, ); } /// generated route for -/// [_i32.StatsStreamsPage] -class StatsStreamsRoute extends _i39.PageRouteInfo { - const StatsStreamsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i33.StatsStreamsPage] +class StatsStreamsRoute extends _i40.PageRouteInfo { + const StatsStreamsRoute({List<_i40.PageRouteInfo>? children}) : super( StatsStreamsRoute.name, initialChildren: children, @@ -879,21 +933,21 @@ class StatsStreamsRoute extends _i39.PageRouteInfo { static const String name = 'StatsStreamsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i32.StatsStreamsPage(); + return const _i33.StatsStreamsPage(); }, ); } /// generated route for -/// [_i33.TrackPage] -class TrackRoute extends _i39.PageRouteInfo { +/// [_i34.TrackPage] +class TrackRoute extends _i40.PageRouteInfo { TrackRoute({ - _i42.Key? key, + _i43.Key? key, required String trackId, - List<_i39.PageRouteInfo>? children, + List<_i40.PageRouteInfo>? children, }) : super( TrackRoute.name, args: TrackRouteArgs( @@ -906,13 +960,13 @@ class TrackRoute extends _i39.PageRouteInfo { static const String name = 'TrackRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { final pathParams = data.inheritedPathParams; final args = data.argsAs( orElse: () => TrackRouteArgs(trackId: pathParams.getString('id'))); - return _i33.TrackPage( + return _i34.TrackPage( key: args.key, trackId: args.trackId, ); @@ -926,7 +980,7 @@ class TrackRouteArgs { required this.trackId, }); - final _i42.Key? key; + final _i43.Key? key; final String trackId; @@ -937,9 +991,9 @@ class TrackRouteArgs { } /// generated route for -/// [_i34.UserAlbumsPage] -class UserAlbumsRoute extends _i39.PageRouteInfo { - const UserAlbumsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i35.UserAlbumsPage] +class UserAlbumsRoute extends _i40.PageRouteInfo { + const UserAlbumsRoute({List<_i40.PageRouteInfo>? children}) : super( UserAlbumsRoute.name, initialChildren: children, @@ -947,18 +1001,18 @@ class UserAlbumsRoute extends _i39.PageRouteInfo { static const String name = 'UserAlbumsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i34.UserAlbumsPage(); + return const _i35.UserAlbumsPage(); }, ); } /// generated route for -/// [_i35.UserArtistsPage] -class UserArtistsRoute extends _i39.PageRouteInfo { - const UserArtistsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i36.UserArtistsPage] +class UserArtistsRoute extends _i40.PageRouteInfo { + const UserArtistsRoute({List<_i40.PageRouteInfo>? children}) : super( UserArtistsRoute.name, initialChildren: children, @@ -966,18 +1020,18 @@ class UserArtistsRoute extends _i39.PageRouteInfo { static const String name = 'UserArtistsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i35.UserArtistsPage(); + return const _i36.UserArtistsPage(); }, ); } /// generated route for -/// [_i36.UserDownloadsPage] -class UserDownloadsRoute extends _i39.PageRouteInfo { - const UserDownloadsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i37.UserDownloadsPage] +class UserDownloadsRoute extends _i40.PageRouteInfo { + const UserDownloadsRoute({List<_i40.PageRouteInfo>? children}) : super( UserDownloadsRoute.name, initialChildren: children, @@ -985,18 +1039,18 @@ class UserDownloadsRoute extends _i39.PageRouteInfo { static const String name = 'UserDownloadsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i36.UserDownloadsPage(); + return const _i37.UserDownloadsPage(); }, ); } /// generated route for -/// [_i37.UserLocalLibraryPage] -class UserLocalLibraryRoute extends _i39.PageRouteInfo { - const UserLocalLibraryRoute({List<_i39.PageRouteInfo>? children}) +/// [_i38.UserLocalLibraryPage] +class UserLocalLibraryRoute extends _i40.PageRouteInfo { + const UserLocalLibraryRoute({List<_i40.PageRouteInfo>? children}) : super( UserLocalLibraryRoute.name, initialChildren: children, @@ -1004,18 +1058,18 @@ class UserLocalLibraryRoute extends _i39.PageRouteInfo { static const String name = 'UserLocalLibraryRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i37.UserLocalLibraryPage(); + return const _i38.UserLocalLibraryPage(); }, ); } /// generated route for -/// [_i38.UserPlaylistsPage] -class UserPlaylistsRoute extends _i39.PageRouteInfo { - const UserPlaylistsRoute({List<_i39.PageRouteInfo>? children}) +/// [_i39.UserPlaylistsPage] +class UserPlaylistsRoute extends _i40.PageRouteInfo { + const UserPlaylistsRoute({List<_i40.PageRouteInfo>? children}) : super( UserPlaylistsRoute.name, initialChildren: children, @@ -1023,10 +1077,10 @@ class UserPlaylistsRoute extends _i39.PageRouteInfo { static const String name = 'UserPlaylistsRoute'; - static _i39.PageInfo page = _i39.PageInfo( + static _i40.PageInfo page = _i40.PageInfo( name, builder: (data) { - return const _i38.UserPlaylistsPage(); + return const _i39.UserPlaylistsPage(); }, ); } diff --git a/lib/models/metadata/fields.dart b/lib/models/metadata/fields.dart new file mode 100644 index 00000000..11d6656d --- /dev/null +++ b/lib/models/metadata/fields.dart @@ -0,0 +1,26 @@ +part of 'metadata.dart'; + +enum FormFieldVariant { text, password, number } + +@Freezed(unionKey: 'objectType') +class MetadataFormFieldObject with _$MetadataFormFieldObject { + @FreezedUnionValue("input") + factory MetadataFormFieldObject.input({ + required String objectType, + required String id, + @Default(FormFieldVariant.text) FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex, + }) = MetadataFormFieldInputObject; + + @FreezedUnionValue("text") + factory MetadataFormFieldObject.text({ + required String objectType, + required String text, + }) = MetadataFormFieldTextObject; + + factory MetadataFormFieldObject.fromJson(Map json) => + _$MetadataFormFieldObjectFromJson(json); +} diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index c625a549..7e3496a4 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -18,6 +18,7 @@ part 'metadata.freezed.dart'; part 'album.dart'; part 'artist.dart'; part 'browse.dart'; +part 'fields.dart'; part 'image.dart'; part 'pagination.dart'; part 'playlist.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 636a092f..89361ff6 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -1469,6 +1469,609 @@ abstract class _SpotubeBrowseSectionObject get copyWith => throw _privateConstructorUsedError; } +MetadataFormFieldObject _$MetadataFormFieldObjectFromJson( + Map json) { + switch (json['objectType']) { + case 'input': + return MetadataFormFieldInputObject.fromJson(json); + case 'text': + return MetadataFormFieldTextObject.fromJson(json); + + default: + throw CheckedFromJsonException( + json, + 'objectType', + 'MetadataFormFieldObject', + 'Invalid union type "${json['objectType']}"!'); + } +} + +/// @nodoc +mixin _$MetadataFormFieldObject { + String get objectType => throw _privateConstructorUsedError; + @optionalTypeArgs + TResult when({ + required TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex) + input, + required TResult Function(String objectType, String text) text, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult? Function(String objectType, String text)? text, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult Function(String objectType, String text)? text, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult map({ + required TResult Function(MetadataFormFieldInputObject value) input, + required TResult Function(MetadataFormFieldTextObject value) text, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MetadataFormFieldInputObject value)? input, + TResult? Function(MetadataFormFieldTextObject value)? text, + }) => + throw _privateConstructorUsedError; + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MetadataFormFieldInputObject value)? input, + TResult Function(MetadataFormFieldTextObject value)? text, + required TResult orElse(), + }) => + throw _privateConstructorUsedError; + + /// Serializes this MetadataFormFieldObject to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MetadataFormFieldObjectCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MetadataFormFieldObjectCopyWith<$Res> { + factory $MetadataFormFieldObjectCopyWith(MetadataFormFieldObject value, + $Res Function(MetadataFormFieldObject) then) = + _$MetadataFormFieldObjectCopyWithImpl<$Res, MetadataFormFieldObject>; + @useResult + $Res call({String objectType}); +} + +/// @nodoc +class _$MetadataFormFieldObjectCopyWithImpl<$Res, + $Val extends MetadataFormFieldObject> + implements $MetadataFormFieldObjectCopyWith<$Res> { + _$MetadataFormFieldObjectCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? objectType = null, + }) { + return _then(_value.copyWith( + objectType: null == objectType + ? _value.objectType + : objectType // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MetadataFormFieldInputObjectImplCopyWith<$Res> + implements $MetadataFormFieldObjectCopyWith<$Res> { + factory _$$MetadataFormFieldInputObjectImplCopyWith( + _$MetadataFormFieldInputObjectImpl value, + $Res Function(_$MetadataFormFieldInputObjectImpl) then) = + __$$MetadataFormFieldInputObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call( + {String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex}); +} + +/// @nodoc +class __$$MetadataFormFieldInputObjectImplCopyWithImpl<$Res> + extends _$MetadataFormFieldObjectCopyWithImpl<$Res, + _$MetadataFormFieldInputObjectImpl> + implements _$$MetadataFormFieldInputObjectImplCopyWith<$Res> { + __$$MetadataFormFieldInputObjectImplCopyWithImpl( + _$MetadataFormFieldInputObjectImpl _value, + $Res Function(_$MetadataFormFieldInputObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? objectType = null, + Object? id = null, + Object? variant = null, + Object? placeholder = freezed, + Object? defaultValue = freezed, + Object? required = freezed, + Object? regex = freezed, + }) { + return _then(_$MetadataFormFieldInputObjectImpl( + objectType: null == objectType + ? _value.objectType + : objectType // ignore: cast_nullable_to_non_nullable + as String, + id: null == id + ? _value.id + : id // ignore: cast_nullable_to_non_nullable + as String, + variant: null == variant + ? _value.variant + : variant // ignore: cast_nullable_to_non_nullable + as FormFieldVariant, + placeholder: freezed == placeholder + ? _value.placeholder + : placeholder // ignore: cast_nullable_to_non_nullable + as String?, + defaultValue: freezed == defaultValue + ? _value.defaultValue + : defaultValue // ignore: cast_nullable_to_non_nullable + as String?, + required: freezed == required + ? _value.required + : required // ignore: cast_nullable_to_non_nullable + as bool?, + regex: freezed == regex + ? _value.regex + : regex // ignore: cast_nullable_to_non_nullable + as String?, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MetadataFormFieldInputObjectImpl + implements MetadataFormFieldInputObject { + _$MetadataFormFieldInputObjectImpl( + {required this.objectType, + required this.id, + this.variant = FormFieldVariant.text, + this.placeholder, + this.defaultValue, + this.required, + this.regex}); + + factory _$MetadataFormFieldInputObjectImpl.fromJson( + Map json) => + _$$MetadataFormFieldInputObjectImplFromJson(json); + + @override + final String objectType; + @override + final String id; + @override + @JsonKey() + final FormFieldVariant variant; + @override + final String? placeholder; + @override + final String? defaultValue; + @override + final bool? required; + @override + final String? regex; + + @override + String toString() { + return 'MetadataFormFieldObject.input(objectType: $objectType, id: $id, variant: $variant, placeholder: $placeholder, defaultValue: $defaultValue, required: $required, regex: $regex)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MetadataFormFieldInputObjectImpl && + (identical(other.objectType, objectType) || + other.objectType == objectType) && + (identical(other.id, id) || other.id == id) && + (identical(other.variant, variant) || other.variant == variant) && + (identical(other.placeholder, placeholder) || + other.placeholder == placeholder) && + (identical(other.defaultValue, defaultValue) || + other.defaultValue == defaultValue) && + (identical(other.required, required) || + other.required == required) && + (identical(other.regex, regex) || other.regex == regex)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, objectType, id, variant, + placeholder, defaultValue, required, regex); + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MetadataFormFieldInputObjectImplCopyWith< + _$MetadataFormFieldInputObjectImpl> + get copyWith => __$$MetadataFormFieldInputObjectImplCopyWithImpl< + _$MetadataFormFieldInputObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex) + input, + required TResult Function(String objectType, String text) text, + }) { + return input( + objectType, id, variant, placeholder, defaultValue, required, regex); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult? Function(String objectType, String text)? text, + }) { + return input?.call( + objectType, id, variant, placeholder, defaultValue, required, regex); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult Function(String objectType, String text)? text, + required TResult orElse(), + }) { + if (input != null) { + return input( + objectType, id, variant, placeholder, defaultValue, required, regex); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MetadataFormFieldInputObject value) input, + required TResult Function(MetadataFormFieldTextObject value) text, + }) { + return input(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MetadataFormFieldInputObject value)? input, + TResult? Function(MetadataFormFieldTextObject value)? text, + }) { + return input?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MetadataFormFieldInputObject value)? input, + TResult Function(MetadataFormFieldTextObject value)? text, + required TResult orElse(), + }) { + if (input != null) { + return input(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MetadataFormFieldInputObjectImplToJson( + this, + ); + } +} + +abstract class MetadataFormFieldInputObject implements MetadataFormFieldObject { + factory MetadataFormFieldInputObject( + {required final String objectType, + required final String id, + final FormFieldVariant variant, + final String? placeholder, + final String? defaultValue, + final bool? required, + final String? regex}) = _$MetadataFormFieldInputObjectImpl; + + factory MetadataFormFieldInputObject.fromJson(Map json) = + _$MetadataFormFieldInputObjectImpl.fromJson; + + @override + String get objectType; + String get id; + FormFieldVariant get variant; + String? get placeholder; + String? get defaultValue; + bool? get required; + String? get regex; + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MetadataFormFieldInputObjectImplCopyWith< + _$MetadataFormFieldInputObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class _$$MetadataFormFieldTextObjectImplCopyWith<$Res> + implements $MetadataFormFieldObjectCopyWith<$Res> { + factory _$$MetadataFormFieldTextObjectImplCopyWith( + _$MetadataFormFieldTextObjectImpl value, + $Res Function(_$MetadataFormFieldTextObjectImpl) then) = + __$$MetadataFormFieldTextObjectImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String objectType, String text}); +} + +/// @nodoc +class __$$MetadataFormFieldTextObjectImplCopyWithImpl<$Res> + extends _$MetadataFormFieldObjectCopyWithImpl<$Res, + _$MetadataFormFieldTextObjectImpl> + implements _$$MetadataFormFieldTextObjectImplCopyWith<$Res> { + __$$MetadataFormFieldTextObjectImplCopyWithImpl( + _$MetadataFormFieldTextObjectImpl _value, + $Res Function(_$MetadataFormFieldTextObjectImpl) _then) + : super(_value, _then); + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? objectType = null, + Object? text = null, + }) { + return _then(_$MetadataFormFieldTextObjectImpl( + objectType: null == objectType + ? _value.objectType + : objectType // ignore: cast_nullable_to_non_nullable + as String, + text: null == text + ? _value.text + : text // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MetadataFormFieldTextObjectImpl implements MetadataFormFieldTextObject { + _$MetadataFormFieldTextObjectImpl( + {required this.objectType, required this.text}); + + factory _$MetadataFormFieldTextObjectImpl.fromJson( + Map json) => + _$$MetadataFormFieldTextObjectImplFromJson(json); + + @override + final String objectType; + @override + final String text; + + @override + String toString() { + return 'MetadataFormFieldObject.text(objectType: $objectType, text: $text)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MetadataFormFieldTextObjectImpl && + (identical(other.objectType, objectType) || + other.objectType == objectType) && + (identical(other.text, text) || other.text == text)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => Object.hash(runtimeType, objectType, text); + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> + get copyWith => __$$MetadataFormFieldTextObjectImplCopyWithImpl< + _$MetadataFormFieldTextObjectImpl>(this, _$identity); + + @override + @optionalTypeArgs + TResult when({ + required TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex) + input, + required TResult Function(String objectType, String text) text, + }) { + return text(objectType, this.text); + } + + @override + @optionalTypeArgs + TResult? whenOrNull({ + TResult? Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult? Function(String objectType, String text)? text, + }) { + return text?.call(objectType, this.text); + } + + @override + @optionalTypeArgs + TResult maybeWhen({ + TResult Function( + String objectType, + String id, + FormFieldVariant variant, + String? placeholder, + String? defaultValue, + bool? required, + String? regex)? + input, + TResult Function(String objectType, String text)? text, + required TResult orElse(), + }) { + if (text != null) { + return text(objectType, this.text); + } + return orElse(); + } + + @override + @optionalTypeArgs + TResult map({ + required TResult Function(MetadataFormFieldInputObject value) input, + required TResult Function(MetadataFormFieldTextObject value) text, + }) { + return text(this); + } + + @override + @optionalTypeArgs + TResult? mapOrNull({ + TResult? Function(MetadataFormFieldInputObject value)? input, + TResult? Function(MetadataFormFieldTextObject value)? text, + }) { + return text?.call(this); + } + + @override + @optionalTypeArgs + TResult maybeMap({ + TResult Function(MetadataFormFieldInputObject value)? input, + TResult Function(MetadataFormFieldTextObject value)? text, + required TResult orElse(), + }) { + if (text != null) { + return text(this); + } + return orElse(); + } + + @override + Map toJson() { + return _$$MetadataFormFieldTextObjectImplToJson( + this, + ); + } +} + +abstract class MetadataFormFieldTextObject implements MetadataFormFieldObject { + factory MetadataFormFieldTextObject( + {required final String objectType, + required final String text}) = _$MetadataFormFieldTextObjectImpl; + + factory MetadataFormFieldTextObject.fromJson(Map json) = + _$MetadataFormFieldTextObjectImpl.fromJson; + + @override + String get objectType; + String get text; + + /// Create a copy of MetadataFormFieldObject + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MetadataFormFieldTextObjectImplCopyWith<_$MetadataFormFieldTextObjectImpl> + get copyWith => throw _privateConstructorUsedError; +} + SpotubeImageObject _$SpotubeImageObjectFromJson(Map json) { return _SpotubeImageObject.fromJson(json); } diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index 75e2ec18..ffafa931 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -153,6 +153,52 @@ Map _$$SpotubeBrowseSectionObjectImplToJson( 'items': instance.items.map(toJsonT).toList(), }; +_$MetadataFormFieldInputObjectImpl _$$MetadataFormFieldInputObjectImplFromJson( + Map json) => + _$MetadataFormFieldInputObjectImpl( + objectType: json['objectType'] as String, + id: json['id'] as String, + variant: + $enumDecodeNullable(_$FormFieldVariantEnumMap, json['variant']) ?? + FormFieldVariant.text, + placeholder: json['placeholder'] as String?, + defaultValue: json['defaultValue'] as String?, + required: json['required'] as bool?, + regex: json['regex'] as String?, + ); + +Map _$$MetadataFormFieldInputObjectImplToJson( + _$MetadataFormFieldInputObjectImpl instance) => + { + 'objectType': instance.objectType, + 'id': instance.id, + 'variant': _$FormFieldVariantEnumMap[instance.variant]!, + 'placeholder': instance.placeholder, + 'defaultValue': instance.defaultValue, + 'required': instance.required, + 'regex': instance.regex, + }; + +const _$FormFieldVariantEnumMap = { + FormFieldVariant.text: 'text', + FormFieldVariant.password: 'password', + FormFieldVariant.number: 'number', +}; + +_$MetadataFormFieldTextObjectImpl _$$MetadataFormFieldTextObjectImplFromJson( + Map json) => + _$MetadataFormFieldTextObjectImpl( + objectType: json['objectType'] as String, + text: json['text'] as String, + ); + +Map _$$MetadataFormFieldTextObjectImplToJson( + _$MetadataFormFieldTextObjectImpl instance) => + { + 'objectType': instance.objectType, + 'text': instance.text, + }; + _$SpotubeImageObjectImpl _$$SpotubeImageObjectImplFromJson(Map json) => _$SpotubeImageObjectImpl( url: json['url'] as String, diff --git a/lib/pages/settings/metadata/metadata_form.dart b/lib/pages/settings/metadata/metadata_form.dart new file mode 100644 index 00000000..3cd29937 --- /dev/null +++ b/lib/pages/settings/metadata/metadata_form.dart @@ -0,0 +1,141 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:flutter_form_builder/flutter_form_builder.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; +import 'package:form_builder_validators/form_builder_validators.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/components/titlebar/titlebar.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/metadata/metadata.dart'; + +@RoutePage() +class SettingsMetadataProviderFormPage extends HookConsumerWidget { + final String title; + final List fields; + const SettingsMetadataProviderFormPage({ + super.key, + required this.title, + required this.fields, + }); + + @override + Widget build(BuildContext context, ref) { + final formKey = useMemoized(() => GlobalKey(), []); + + return Scaffold( + headers: [ + TitleBar( + title: Text(title), + ), + ], + child: FormBuilder( + key: formKey, + child: Center( + child: Container( + padding: const EdgeInsets.all(16), + constraints: const BoxConstraints(maxWidth: 600), + child: CustomScrollView( + shrinkWrap: true, + slivers: [ + SliverToBoxAdapter( + child: Text( + title, + textAlign: TextAlign.center, + style: context.theme.typography.h2, + ), + ), + const SliverGap(24), + SliverList.separated( + itemCount: fields.length, + separatorBuilder: (context, index) => const Gap(12), + itemBuilder: (context, index) { + if (fields[index] is MetadataFormFieldTextObject) { + final field = + fields[index] as MetadataFormFieldTextObject; + return MarkdownBody(data: field.text); + } + + final field = fields[index] as MetadataFormFieldInputObject; + return FormBuilderField( + name: field.id, + initialValue: field.defaultValue, + validator: FormBuilderValidators.compose([ + if (field.required == true) + FormBuilderValidators.required( + errorText: 'This field is required', + ), + if (field.regex != null) + FormBuilderValidators.match( + RegExp(field.regex!), + errorText: + "Input doesn't match the required format", + ), + ]), + builder: (formField) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4, + children: [ + TextField( + placeholder: field.placeholder == null + ? null + : Text(field.placeholder!), + initialValue: formField.value, + onChanged: (value) { + formField.didChange(value); + }, + obscureText: + field.variant == FormFieldVariant.password, + keyboardType: + field.variant == FormFieldVariant.number + ? TextInputType.number + : TextInputType.text, + features: [ + if (field.variant == FormFieldVariant.password) + const InputFeature.passwordToggle(), + ], + ), + if (formField.hasError) + Text( + formField.errorText ?? '', + style: const TextStyle( + color: Colors.red, fontSize: 12), + ), + ], + ); + }, + ); + }, + ), + const SliverGap(24), + SliverToBoxAdapter( + child: Button.primary( + onPressed: () { + if (formKey.currentState?.saveAndValidate() != true) { + return; + } + + final data = formKey.currentState!.value.entries + .map((e) => { + "id": e.key, + "value": e.value, + }) + .toList(); + + context.router.maybePop(data); + }, + child: Text(context.l10n.submit), + ), + ), + const SliverGap(200) + ], + ), + ), + ), + ), + ); + } +} diff --git a/lib/services/metadata/metadata.dart b/lib/services/metadata/metadata.dart index 98dad35a..14132cf3 100644 --- a/lib/services/metadata/metadata.dart +++ b/lib/services/metadata/metadata.dart @@ -8,6 +8,7 @@ import 'package:hetu_std/hetu_std.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotube/collections/routes.dart'; +import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/services/metadata/apis/localstorage.dart'; @@ -57,6 +58,20 @@ class MetadataPlugin { onNavigatorPop: () { pageContext?.maybePop(); }, + onShowForm: (title, fields) async { + if (rootNavigatorKey.currentContext == null) { + return []; + } + + return await rootNavigatorKey.currentContext!.router + .push>?>( + SettingsMetadataProviderFormRoute( + title: title, + fields: + fields.map((e) => MetadataFormFieldObject.fromJson(e)).toList(), + ), + ); + }, ); await HetuStdLoader.loadBytecodeFlutter(hetu); diff --git a/pubspec.lock b/pubspec.lock index 3cc1d962..680e068f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -938,6 +938,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_markdown_plus: + dependency: "direct main" + description: + name: flutter_markdown_plus + sha256: fe74214c5ac2f850d93efda290dcde3f18006e90a87caa9e3e6c13222a5db4de + url: "https://pub.dev" + source: hosted + version: "1.0.3" flutter_native_splash: dependency: "direct main" description: @@ -1209,7 +1217,7 @@ packages: description: path: "." ref: main - resolved-ref: eeb574c3fac0da07ba93c9d972b7eb38960538d2 + resolved-ref: c4895250ee45a59c88770f97abebc9e9bbb62259 url: "https://github.com/KRTirtho/hetu_spotube_plugin.git" source: git version: "0.0.1" @@ -1523,6 +1531,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.1.3-main.0" + markdown: + dependency: transitive + description: + name: markdown + sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + url: "https://pub.dev" + source: hosted + version: "7.3.0" matcher: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 545c608e..a791605a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -156,6 +156,7 @@ dependencies: url: https://github.com/KRTirtho/hetu_spotube_plugin.git ref: main get_it: ^8.0.3 + flutter_markdown_plus: ^1.0.3 dev_dependencies: build_runner: ^2.4.13 From 412c427cecf0649b09e8b72af41224357b4b762a Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sat, 5 Jul 2025 21:46:35 +0600 Subject: [PATCH 34/60] chore: fix bad certificate error --- lib/collections/http-override.dart | 17 +++++++++++++++++ lib/main.dart | 4 ++++ 2 files changed, 21 insertions(+) create mode 100644 lib/collections/http-override.dart diff --git a/lib/collections/http-override.dart b/lib/collections/http-override.dart new file mode 100644 index 00000000..3bf4f30e --- /dev/null +++ b/lib/collections/http-override.dart @@ -0,0 +1,17 @@ +import 'dart:io'; + +const allowList = [ + "spotify.com", +]; + +class BadCertificateAllowlistOverrides extends HttpOverrides { + @override + HttpClient createHttpClient(SecurityContext? context) { + return super.createHttpClient(context) + ..badCertificateCallback = (X509Certificate cert, String host, int port) { + return allowList.any((allowedHost) { + return host.endsWith(allowedHost); + }); + }; + } +} diff --git a/lib/main.dart b/lib/main.dart index 67793058..d575eda1 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:ui'; +import 'dart:io'; import 'package:desktop_webview_window/desktop_webview_window.dart'; import 'package:flutter/foundation.dart'; @@ -16,6 +17,7 @@ import 'package:media_kit/media_kit.dart'; import 'package:metadata_god/metadata_god.dart'; import 'package:smtc_windows/smtc_windows.dart'; import 'package:spotube/collections/env.dart'; +import 'package:spotube/collections/http-override.dart'; import 'package:spotube/collections/intents.dart'; import 'package:spotube/collections/routes.dart'; import 'package:spotube/hooks/configurators/use_close_behavior.dart'; @@ -65,6 +67,8 @@ Future main(List rawArgs) async { AppLogger.runZoned(() async { final widgetsBinding = WidgetsFlutterBinding.ensureInitialized(); + HttpOverrides.global = BadCertificateAllowlistOverrides(); + // await registerWindowsScheme("spotify"); tz.initializeTimeZones(); From 7de6423935579a5298ff01f2c066ed9fbebc20b7 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 13 Jul 2025 15:11:56 +0600 Subject: [PATCH 35/60] feat: add support for entity specific search --- lib/modules/player/sibling_tracks_sheet.dart | 4 +- lib/modules/search/loading.dart | 68 ++++++++ .../search/sections/albums.dart | 0 .../search/sections/artists.dart | 0 .../search/sections/playlists.dart | 0 .../search/sections/tracks.dart | 0 lib/pages/artist/section/header.dart | 8 +- lib/pages/search/search.dart | 145 ++++++++---------- lib/pages/search/tabs/albums.dart | 48 ++++++ lib/pages/search/tabs/all.dart | 48 ++++++ lib/pages/search/tabs/artists.dart | 94 ++++++++++++ lib/pages/search/tabs/playlists.dart | 48 ++++++ lib/pages/search/tabs/tracks.dart | 119 ++++++++++++++ .../metadata_plugin/search/albums.dart | 46 ++++++ lib/provider/metadata_plugin/search/all.dart | 11 ++ .../metadata_plugin/search/artists.dart | 46 ++++++ .../metadata_plugin/search/playlists.dart | 48 ++++++ .../metadata_plugin/search/tracks.dart | 46 ++++++ lib/services/metadata/endpoints/search.dart | 4 + pubspec.lock | 2 +- 20 files changed, 696 insertions(+), 89 deletions(-) create mode 100644 lib/modules/search/loading.dart rename lib/{pages => modules}/search/sections/albums.dart (100%) rename lib/{pages => modules}/search/sections/artists.dart (100%) rename lib/{pages => modules}/search/sections/playlists.dart (100%) rename lib/{pages => modules}/search/sections/tracks.dart (100%) create mode 100644 lib/pages/search/tabs/albums.dart create mode 100644 lib/pages/search/tabs/all.dart create mode 100644 lib/pages/search/tabs/artists.dart create mode 100644 lib/pages/search/tabs/playlists.dart create mode 100644 lib/pages/search/tabs/tracks.dart create mode 100644 lib/provider/metadata_plugin/search/albums.dart create mode 100644 lib/provider/metadata_plugin/search/artists.dart create mode 100644 lib/provider/metadata_plugin/search/playlists.dart create mode 100644 lib/provider/metadata_plugin/search/tracks.dart diff --git a/lib/modules/player/sibling_tracks_sheet.dart b/lib/modules/player/sibling_tracks_sheet.dart index fee0c46a..4e36aa09 100644 --- a/lib/modules/player/sibling_tracks_sheet.dart +++ b/lib/modules/player/sibling_tracks_sheet.dart @@ -107,7 +107,7 @@ class SiblingTracksSheet extends HookConsumerWidget { return siblingType.info; })); - final activeSourceInfo = activeTrackSource as TrackSourceInfo; + final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; return results ..removeWhere((element) => element.id == activeSourceInfo.id) @@ -127,7 +127,7 @@ class SiblingTracksSheet extends HookConsumerWidget { return siblingType.info; }), ); - final activeSourceInfo = activeTrackSource as TrackSourceInfo; + final activeSourceInfo = activeTrackSource?.info as TrackSourceInfo; return searchResults ..removeWhere((element) => element.id == activeSourceInfo.id) ..insert( diff --git a/lib/modules/search/loading.dart b/lib/modules/search/loading.dart new file mode 100644 index 00000000..8ca2820f --- /dev/null +++ b/lib/modules/search/loading.dart @@ -0,0 +1,68 @@ +import 'package:flutter_undraw/flutter_undraw.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/pages/search/search.dart'; + +class SearchPlaceholder extends HookConsumerWidget { + final AsyncValue snapshot; + final Widget child; + const SearchPlaceholder({ + super.key, + required this.child, + required this.snapshot, + }); + + @override + Widget build(BuildContext context, ref) { + final theme = context.theme; + final mediaQuery = MediaQuery.sizeOf(context); + + final searchTerm = ref.watch(searchTermStateProvider); + + return switch ((searchTerm.isEmpty, snapshot.isLoading)) { + (true, false) => Column( + children: [ + SizedBox( + height: mediaQuery.height * 0.2, + ), + Undraw( + illustration: UndrawIllustration.explore, + color: theme.colorScheme.primary, + height: 200 * theme.scaling, + ), + const SizedBox(height: 20), + Text(context.l10n.search_to_get_results).large(), + ], + ), + (false, true) => Container( + constraints: BoxConstraints( + maxWidth: + mediaQuery.lgAndUp ? mediaQuery.width * 0.5 : mediaQuery.width, + ), + padding: const EdgeInsets.symmetric( + horizontal: 20, + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text( + context.l10n.crunching_results, + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w900, + color: theme.colorScheme.foreground.withValues(alpha: 0.7), + ), + ), + const SizedBox(height: 20), + const LinearProgressIndicator(), + ], + ), + ), + _ => child, + }; + } +} diff --git a/lib/pages/search/sections/albums.dart b/lib/modules/search/sections/albums.dart similarity index 100% rename from lib/pages/search/sections/albums.dart rename to lib/modules/search/sections/albums.dart diff --git a/lib/pages/search/sections/artists.dart b/lib/modules/search/sections/artists.dart similarity index 100% rename from lib/pages/search/sections/artists.dart rename to lib/modules/search/sections/artists.dart diff --git a/lib/pages/search/sections/playlists.dart b/lib/modules/search/sections/playlists.dart similarity index 100% rename from lib/pages/search/sections/playlists.dart rename to lib/modules/search/sections/playlists.dart diff --git a/lib/pages/search/sections/tracks.dart b/lib/modules/search/sections/tracks.dart similarity index 100% rename from lib/pages/search/sections/tracks.dart rename to lib/modules/search/sections/tracks.dart diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index b11e8d66..2ce9178c 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -195,9 +195,11 @@ class ArtistPageHeader extends HookConsumerWidget { Flexible( child: AutoSizeText( context.l10n.followers( - PrimitiveUtils.toReadableNumber( - artist.followers!.toDouble(), - ), + artist.followers == null + ? double.infinity + : PrimitiveUtils.toReadableNumber( + artist.followers!.toDouble(), + ), ), maxLines: 1, overflow: TextOverflow.ellipsis, diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 955ff59b..0db34810 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -1,5 +1,4 @@ import 'package:flutter/services.dart'; -import 'package:flutter_undraw/flutter_undraw.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; @@ -8,16 +7,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; -import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; -import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; +import 'package:spotube/extensions/string.dart'; import 'package:spotube/hooks/controllers/use_shadcn_text_editing_controller.dart'; -import 'package:spotube/pages/search/sections/albums.dart'; -import 'package:spotube/pages/search/sections/artists.dart'; -import 'package:spotube/pages/search/sections/playlists.dart'; -import 'package:spotube/pages/search/sections/tracks.dart'; +import 'package:spotube/pages/search/tabs/albums.dart'; +import 'package:spotube/pages/search/tabs/all.dart'; +import 'package:spotube/pages/search/tabs/artists.dart'; +import 'package:spotube/pages/search/tabs/playlists.dart'; +import 'package:spotube/pages/search/tabs/tracks.dart'; import 'package:spotube/provider/metadata_plugin/auth.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; @@ -35,18 +34,23 @@ class SearchPage extends HookConsumerWidget { @override Widget build(BuildContext context, ref) { - final theme = Theme.of(context); - final mediaQuery = MediaQuery.sizeOf(context); - - final scrollController = useScrollController(); final controller = useShadcnTextEditingController(); final focusNode = useFocusNode(); final authenticated = ref.watch(metadataPluginAuthenticatedProvider); final searchTerm = ref.watch(searchTermStateProvider); - final searchSnapshot = - ref.watch(metadataPluginSearchAllProvider(searchTerm)); + final searchChipSnapshot = ref.watch(metadataPluginSearchChipsProvider); + final selectedChip = useState( + searchChipSnapshot.asData?.value.first ?? "all", + ); + + ref.listen( + metadataPluginSearchChipsProvider, + (previous, next) { + selectedChip.value = next.asData?.value.first ?? "all"; + }, + ); useEffect(() { controller.text = searchTerm; @@ -88,7 +92,10 @@ class SearchPage extends HookConsumerWidget { children: [ Expanded( child: Padding( - padding: const EdgeInsets.all(20), + padding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 10, + ), child: ListenableBuilder( listenable: controller, builder: (context, _) { @@ -168,78 +175,50 @@ class SearchPage extends HookConsumerWidget { ), ], ), + Row( + spacing: 8, + children: [ + const Gap(12), + if (searchChipSnapshot.asData?.value != null) + for (final chip in searchChipSnapshot.asData!.value) + Chip( + style: selectedChip.value == chip + ? ButtonVariance.primary.copyWith( + decoration: (context, states, value) { + return ButtonVariance.primary + .decoration(context, states) + .copyWithIfBoxDecoration( + borderRadius: + BorderRadius.circular(100), + ); + }, + ) + : ButtonVariance.secondary.copyWith( + decoration: (context, states, value) { + return ButtonVariance.secondary + .decoration(context, states) + .copyWithIfBoxDecoration( + borderRadius: + BorderRadius.circular(100), + ); + }, + ), + child: Text(chip.capitalize()), + onPressed: () { + selectedChip.value = chip; + }, + ), + ], + ), Expanded( child: AnimatedSwitcher( duration: const Duration(milliseconds: 300), - child: switch (( - searchTerm.isEmpty, - searchSnapshot.isLoading - )) { - (true, false) => Column( - children: [ - SizedBox( - height: mediaQuery.height * 0.2, - ), - Undraw( - illustration: UndrawIllustration.explore, - color: theme.colorScheme.primary, - height: 200 * theme.scaling, - ), - const SizedBox(height: 20), - Text(context.l10n.search_to_get_results) - .large(), - ], - ), - (false, true) => Container( - constraints: BoxConstraints( - maxWidth: mediaQuery.lgAndUp - ? mediaQuery.width * 0.5 - : mediaQuery.width, - ), - padding: const EdgeInsets.symmetric( - horizontal: 20, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text( - context.l10n.crunching_results, - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w900, - color: theme.colorScheme.foreground - .withValues(alpha: 0.7), - ), - ), - const SizedBox(height: 20), - const LinearProgressIndicator(), - ], - ), - ), - _ => InterScrollbar( - controller: scrollController, - child: SingleChildScrollView( - controller: scrollController, - child: const Padding( - padding: EdgeInsets.symmetric(vertical: 8), - child: SafeArea( - child: Column( - crossAxisAlignment: - CrossAxisAlignment.start, - children: [ - SearchTracksSection(), - SearchPlaylistsSection(), - Gap(20), - SearchArtistsSection(), - Gap(20), - SearchAlbumsSection(), - ], - ), - ), - ), - ), - ), + child: switch (selectedChip.value) { + "tracks" => const SearchPageTracksTab(), + "albums" => const SearchPageAlbumsTab(), + "artists" => const SearchPageArtistsTab(), + "playlists" => const SearchPagePlaylistsTab(), + _ => const SearchPageAllTab(), }, ), ), diff --git a/lib/pages/search/tabs/albums.dart b/lib/pages/search/tabs/albums.dart new file mode 100644 index 00000000..19781c05 --- /dev/null +++ b/lib/pages/search/tabs/albums.dart @@ -0,0 +1,48 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/playbutton_view/playbutton_view.dart'; +import 'package:spotube/modules/album/album_card.dart'; +import 'package:spotube/modules/search/loading.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/provider/metadata_plugin/search/albums.dart'; + +class SearchPageAlbumsTab extends HookConsumerWidget { + const SearchPageAlbumsTab({super.key}); + + @override + Widget build(BuildContext context, ref) { + final controller = useScrollController(); + + final searchTerm = ref.watch(searchTermStateProvider); + final searchAlbumsSnapshot = + ref.watch(metadataPluginSearchAlbumsProvider(searchTerm)); + final searchAlbumsNotifier = + ref.read(metadataPluginSearchAlbumsProvider(searchTerm).notifier); + final searchAlbums = + searchAlbumsSnapshot.asData?.value.items ?? [FakeData.albumSimple]; + + return SearchPlaceholder( + snapshot: searchAlbumsSnapshot, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: CustomScrollView( + slivers: [ + PlaybuttonView( + controller: controller, + itemCount: searchAlbums.length, + hasMore: searchAlbumsSnapshot.asData?.value.hasMore == true, + isLoading: searchAlbumsSnapshot.isLoading, + onRequestMore: searchAlbumsNotifier.fetchMore, + gridItemBuilder: (context, index) => + AlbumCard(searchAlbums[index]), + listItemBuilder: (context, index) => + AlbumCard.tile(searchAlbums[index]), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/search/tabs/all.dart b/lib/pages/search/tabs/all.dart new file mode 100644 index 00000000..42ff1e69 --- /dev/null +++ b/lib/pages/search/tabs/all.dart @@ -0,0 +1,48 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; +import 'package:spotube/modules/search/loading.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/modules/search/sections/albums.dart'; +import 'package:spotube/modules/search/sections/artists.dart'; +import 'package:spotube/modules/search/sections/playlists.dart'; +import 'package:spotube/modules/search/sections/tracks.dart'; +import 'package:spotube/provider/metadata_plugin/search/all.dart'; + +class SearchPageAllTab extends HookConsumerWidget { + const SearchPageAllTab({super.key}); + + @override + Widget build(BuildContext context, ref) { + final scrollController = ScrollController(); + final searchTerm = ref.watch(searchTermStateProvider); + final searchSnapshot = + ref.watch(metadataPluginSearchAllProvider(searchTerm)); + + return SearchPlaceholder( + snapshot: searchSnapshot, + child: InterScrollbar( + controller: scrollController, + child: SingleChildScrollView( + controller: scrollController, + child: const Padding( + padding: EdgeInsets.symmetric(vertical: 8), + child: SafeArea( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SearchTracksSection(), + SearchPlaylistsSection(), + Gap(20), + SearchArtistsSection(), + Gap(20), + SearchAlbumsSection(), + ], + ), + ), + ), + ), + ), + ); + } +} diff --git a/lib/pages/search/tabs/artists.dart b/lib/pages/search/tabs/artists.dart new file mode 100644 index 00000000..59c77a70 --- /dev/null +++ b/lib/pages/search/tabs/artists.dart @@ -0,0 +1,94 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:flutter_undraw/flutter_undraw.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/waypoint.dart'; +import 'package:spotube/extensions/constrains.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/modules/artist/artist_card.dart'; +import 'package:spotube/modules/search/loading.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/provider/metadata_plugin/search/artists.dart'; + +class SearchPageArtistsTab extends HookConsumerWidget { + const SearchPageArtistsTab({super.key}); + + @override + Widget build(BuildContext context, ref) { + final controller = useScrollController(); + + final searchTerm = ref.watch(searchTermStateProvider); + final searchArtistsSnapshot = + ref.watch(metadataPluginSearchArtistsProvider(searchTerm)); + final searchArtistsNotifier = + ref.read(metadataPluginSearchArtistsProvider(searchTerm).notifier); + final searchArtists = searchArtistsSnapshot.asData?.value.items ?? []; + + return SearchPlaceholder( + snapshot: searchArtistsSnapshot, + child: AnimatedSwitcher( + duration: const Duration(milliseconds: 300), + child: LayoutBuilder(builder: (context, constrains) { + if (searchArtistsSnapshot.hasValue && searchArtists.isEmpty) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 8), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 10, + children: [ + Undraw( + height: 200 * context.theme.scaling, + illustration: UndrawIllustration.taken, + color: Theme.of(context).colorScheme.primary, + ), + Text( + context.l10n.nothing_found, + textAlign: TextAlign.center, + ).muted().small() + ], + ), + ); + } + + return GridView.builder( + padding: const EdgeInsets.all(16), + itemCount: searchArtists.length + 1, + gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( + maxCrossAxisExtent: 200, + mainAxisExtent: constrains.smAndDown ? 225 : 250, + crossAxisSpacing: 8, + mainAxisSpacing: 8, + ), + itemBuilder: (context, index) { + if (searchArtists.isNotEmpty && index == searchArtists.length) { + if (searchArtistsSnapshot.asData?.value.hasMore != true) { + return const SizedBox.shrink(); + } + + return Waypoint( + controller: controller, + isGrid: true, + onTouchEdge: searchArtistsNotifier.fetchMore, + child: Skeletonizer( + enabled: true, + child: ArtistCard(FakeData.artist), + ), + ); + } + + return Skeletonizer( + enabled: searchArtistsSnapshot.isLoading, + child: ArtistCard( + searchArtists.elementAtOrNull(index) ?? FakeData.artist, + ), + ); + }, + ); + }), + ), + ); + } +} diff --git a/lib/pages/search/tabs/playlists.dart b/lib/pages/search/tabs/playlists.dart new file mode 100644 index 00000000..2ea9d430 --- /dev/null +++ b/lib/pages/search/tabs/playlists.dart @@ -0,0 +1,48 @@ +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/playbutton_view/playbutton_view.dart'; +import 'package:spotube/modules/playlist/playlist_card.dart'; +import 'package:spotube/modules/search/loading.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/provider/metadata_plugin/search/playlists.dart'; + +class SearchPagePlaylistsTab extends HookConsumerWidget { + const SearchPagePlaylistsTab({super.key}); + + @override + Widget build(BuildContext context, ref) { + final controller = useScrollController(); + + final searchTerm = ref.watch(searchTermStateProvider); + final searchPlaylistsSnapshot = + ref.watch(metadataPluginSearchPlaylistsProvider(searchTerm)); + final searchPlaylistsNotifier = + ref.read(metadataPluginSearchPlaylistsProvider(searchTerm).notifier); + final searchPlaylists = searchPlaylistsSnapshot.asData?.value.items ?? + [FakeData.playlistSimple]; + + return SearchPlaceholder( + snapshot: searchPlaylistsSnapshot, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: CustomScrollView( + slivers: [ + PlaybuttonView( + controller: controller, + itemCount: searchPlaylists.length, + hasMore: searchPlaylistsSnapshot.asData?.value.hasMore == true, + isLoading: searchPlaylistsSnapshot.isLoading, + onRequestMore: searchPlaylistsNotifier.fetchMore, + gridItemBuilder: (context, index) => + PlaylistCard(searchPlaylists[index]), + listItemBuilder: (context, index) => + PlaylistCard.tile(searchPlaylists[index]), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/search/tabs/tracks.dart b/lib/pages/search/tabs/tracks.dart new file mode 100644 index 00000000..2212c010 --- /dev/null +++ b/lib/pages/search/tabs/tracks.dart @@ -0,0 +1,119 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:skeletonizer/skeletonizer.dart'; +import 'package:spotube/collections/fake.dart'; +import 'package:spotube/components/dialogs/prompt_dialog.dart'; +import 'package:spotube/components/dialogs/select_device_dialog.dart'; +import 'package:spotube/components/track_tile/track_tile.dart'; +import 'package:spotube/extensions/context.dart'; +import 'package:spotube/models/connect/connect.dart'; +import 'package:spotube/modules/search/loading.dart'; +import 'package:spotube/pages/search/search.dart'; +import 'package:spotube/provider/audio_player/audio_player.dart'; +import 'package:spotube/provider/connect/connect.dart'; +import 'package:spotube/provider/metadata_plugin/search/tracks.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; +import 'package:very_good_infinite_list/very_good_infinite_list.dart'; + +class SearchPageTracksTab extends HookConsumerWidget { + const SearchPageTracksTab({super.key}); + + @override + Widget build(BuildContext context, ref) { + final searchTerm = ref.watch(searchTermStateProvider); + final searchTracksSnapshot = + ref.watch(metadataPluginSearchTracksProvider(searchTerm)); + final searchTracksNotifier = + ref.read(metadataPluginSearchTracksProvider(searchTerm).notifier); + final searchTracks = + searchTracksSnapshot.asData?.value.items ?? [FakeData.track]; + + final playlist = ref.watch(audioPlayerProvider); + final playlistNotifier = ref.watch(audioPlayerProvider.notifier); + + return SearchPlaceholder( + snapshot: searchTracksSnapshot, + child: InfiniteList( + itemCount: searchTracksSnapshot.asData?.value.items.length ?? 0, + hasReachedMax: searchTracksSnapshot.asData?.value.hasMore != true, + isLoading: searchTracksSnapshot.isLoading && + !searchTracksSnapshot.isLoadingNextPage, + loadingBuilder: (context) { + return Skeletonizer( + enabled: true, + child: TrackTile(track: FakeData.track, playlist: playlist), + ); + }, + onFetchData: () { + searchTracksNotifier.fetchMore(); + }, + itemBuilder: (context, index) { + final track = searchTracks[index]; + + return TrackTile( + track: track, + playlist: playlist, + index: index, + onTap: () async { + final isRemoteDevice = await showSelectDeviceDialog(context, ref); + + if (isRemoteDevice == null) return; + + if (isRemoteDevice) { + final remotePlayback = ref.read(connectProvider.notifier); + final remotePlaylist = ref.read(queueProvider); + + final isTrackPlaying = + remotePlaylist.activeTrack?.id == track.id; + + if (!isTrackPlaying && context.mounted) { + final shouldPlay = (playlist.tracks.length) > 20 + ? await showPromptDialog( + context: context, + title: context.l10n.playing_track( + track.name, + ), + message: context.l10n.queue_clear_alert( + playlist.tracks.length, + ), + ) + : true; + + if (shouldPlay) { + await remotePlayback.load( + WebSocketLoadEventData.playlist( + tracks: [track], + ), + ); + } + } + } else { + final isTrackPlaying = playlist.activeTrack?.id == track.id; + if (!isTrackPlaying && context.mounted) { + final shouldPlay = (playlist.tracks.length) > 20 + ? await showPromptDialog( + context: context, + title: context.l10n.playing_track( + track.name, + ), + message: context.l10n.queue_clear_alert( + playlist.tracks.length, + ), + ) + : true; + + if (shouldPlay) { + await playlistNotifier.load( + [track], + autoPlay: true, + ); + } + } + } + }, + ); + }, + ), + ); + } +} diff --git a/lib/provider/metadata_plugin/search/albums.dart b/lib/provider/metadata_plugin/search/albums.dart new file mode 100644 index 00000000..40bb62e6 --- /dev/null +++ b/lib/provider/metadata_plugin/search/albums.dart @@ -0,0 +1,46 @@ +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/utils/common.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; + +class MetadataPluginSearchAlbumsNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginSearchAlbumsNotifier() : super(); + + @override + fetch(offset, limit) async { + if (arg.isEmpty) { + return SpotubePaginationResponseObject( + limit: limit, + nextOffset: null, + total: 0, + items: [], + hasMore: false, + ); + } + + final res = await (await metadataPlugin).search.albums( + arg, + offset: offset, + limit: limit, + ); + + return res; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginSearchAlbumsProvider = + AutoDisposeAsyncNotifierProviderFamily, String>( + () => MetadataPluginSearchAlbumsNotifier(), +); diff --git a/lib/provider/metadata_plugin/search/all.dart b/lib/provider/metadata_plugin/search/all.dart index 53c79f7c..92f60175 100644 --- a/lib/provider/metadata_plugin/search/all.dart +++ b/lib/provider/metadata_plugin/search/all.dart @@ -17,3 +17,14 @@ final metadataPluginSearchAllProvider = return metadataPlugin.search.all(query); }, ); + +final metadataPluginSearchChipsProvider = FutureProvider((ref) async { + final metadataPlugin = await ref.watch(metadataPluginProvider.future); + + if (metadataPlugin == null) { + throw MetadataPluginException.noDefaultPlugin( + "No default metadata plugin found", + ); + } + return metadataPlugin.search.chips; +}); diff --git a/lib/provider/metadata_plugin/search/artists.dart b/lib/provider/metadata_plugin/search/artists.dart new file mode 100644 index 00000000..b4d619f7 --- /dev/null +++ b/lib/provider/metadata_plugin/search/artists.dart @@ -0,0 +1,46 @@ +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/utils/common.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; + +class MetadataPluginSearchArtistsNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginSearchArtistsNotifier() : super(); + + @override + fetch(offset, limit) async { + if (arg.isEmpty) { + return SpotubePaginationResponseObject( + limit: limit, + nextOffset: null, + total: 0, + items: [], + hasMore: false, + ); + } + + final res = await (await metadataPlugin).search.artists( + arg, + offset: offset, + limit: limit, + ); + + return res; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginSearchArtistsProvider = + AutoDisposeAsyncNotifierProviderFamily, String>( + () => MetadataPluginSearchArtistsNotifier(), +); diff --git a/lib/provider/metadata_plugin/search/playlists.dart b/lib/provider/metadata_plugin/search/playlists.dart new file mode 100644 index 00000000..dbf54250 --- /dev/null +++ b/lib/provider/metadata_plugin/search/playlists.dart @@ -0,0 +1,48 @@ +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/utils/common.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; + +class MetadataPluginSearchPlaylistsNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginSearchPlaylistsNotifier() : super(); + + @override + fetch(offset, limit) async { + if (arg.isEmpty) { + return SpotubePaginationResponseObject( + limit: limit, + nextOffset: null, + total: 0, + items: [], + hasMore: false, + ); + } + + final res = await (await metadataPlugin).search.playlists( + arg, + offset: offset, + limit: limit, + ); + + return res; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginSearchPlaylistsProvider = + AutoDisposeAsyncNotifierProviderFamily< + MetadataPluginSearchPlaylistsNotifier, + SpotubePaginationResponseObject, + String>( + () => MetadataPluginSearchPlaylistsNotifier(), +); diff --git a/lib/provider/metadata_plugin/search/tracks.dart b/lib/provider/metadata_plugin/search/tracks.dart new file mode 100644 index 00000000..0b6ac141 --- /dev/null +++ b/lib/provider/metadata_plugin/search/tracks.dart @@ -0,0 +1,46 @@ +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/utils/common.dart'; +import 'package:spotube/provider/metadata_plugin/utils/family_paginated.dart'; + +class MetadataPluginSearchTracksNotifier + extends AutoDisposeFamilyPaginatedAsyncNotifier { + MetadataPluginSearchTracksNotifier() : super(); + + @override + fetch(offset, limit) async { + if (arg.isEmpty) { + return SpotubePaginationResponseObject( + limit: limit, + nextOffset: null, + total: 0, + items: [], + hasMore: false, + ); + } + + final tracks = await (await metadataPlugin).search.tracks( + arg, + offset: offset, + limit: limit, + ); + + return tracks; + } + + @override + build(arg) async { + ref.cacheFor(); + + ref.watch(metadataPluginProvider); + return await fetch(0, 20); + } +} + +final metadataPluginSearchTracksProvider = + AutoDisposeAsyncNotifierProviderFamily, String>( + () => MetadataPluginSearchTracksNotifier(), +); diff --git a/lib/services/metadata/endpoints/search.dart b/lib/services/metadata/endpoints/search.dart index be4e8e30..c2e14765 100644 --- a/lib/services/metadata/endpoints/search.dart +++ b/lib/services/metadata/endpoints/search.dart @@ -10,6 +10,10 @@ class MetadataPluginSearchEndpoint { (hetu.fetch("metadataPlugin") as HTInstance).memberGet("search") as HTInstance; + List get chips { + return (hetuMetadataSearch.memberGet("chips") as List).cast(); + } + Future all(String query) async { if (query.isEmpty) { return SpotubeSearchResponseObject( diff --git a/pubspec.lock b/pubspec.lock index 680e068f..f7798e5f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1226,7 +1226,7 @@ packages: description: path: "." ref: main - resolved-ref: d3720be2a92022f7b95a3082d40322d8458c70da + resolved-ref: "7e9032c054c547f7900c9c9fe4b76e29c8ac1cd1" url: "https://github.com/hetu-community/hetu_std.git" source: git version: "1.0.0" From d2827a52d3a217c4458e08eca5a367379a773ebd Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Sun, 13 Jul 2025 21:21:17 +0600 Subject: [PATCH 36/60] chore: upgrade to flutter 3.32.5 --- .fvm/fvm_config.json | 2 +- .fvmrc | 2 +- .github/workflows/pr-lint.yml | 2 +- .github/workflows/spotube-release-binary.yml | 2 +- .vscode/settings.json | 2 +- lib/l10n/generated/app_localizations.dart | 142 ++++++++++++----- lib/l10n/generated/app_localizations_ar.dart | 108 ++++++++----- lib/l10n/generated/app_localizations_bn.dart | 108 ++++++++----- lib/l10n/generated/app_localizations_ca.dart | 126 ++++++++++----- lib/l10n/generated/app_localizations_cs.dart | 105 ++++++++----- lib/l10n/generated/app_localizations_de.dart | 141 +++++++++++------ lib/l10n/generated/app_localizations_en.dart | 105 ++++++++----- lib/l10n/generated/app_localizations_es.dart | 132 ++++++++++------ lib/l10n/generated/app_localizations_eu.dart | 129 ++++++++++------ lib/l10n/generated/app_localizations_fa.dart | 102 ++++++++----- lib/l10n/generated/app_localizations_fi.dart | 108 ++++++++----- lib/l10n/generated/app_localizations_fr.dart | 144 ++++++++++++------ lib/l10n/generated/app_localizations_hi.dart | 120 ++++++++++----- lib/l10n/generated/app_localizations_id.dart | 126 ++++++++++----- lib/l10n/generated/app_localizations_it.dart | 120 ++++++++++----- lib/l10n/generated/app_localizations_ja.dart | 45 ++++-- lib/l10n/generated/app_localizations_ka.dart | 117 +++++++++----- lib/l10n/generated/app_localizations_ko.dart | 51 ++++--- lib/l10n/generated/app_localizations_ne.dart | 138 +++++++++++------ lib/l10n/generated/app_localizations_nl.dart | 120 ++++++++++----- lib/l10n/generated/app_localizations_pl.dart | 123 ++++++++++----- lib/l10n/generated/app_localizations_pt.dart | 117 +++++++++----- lib/l10n/generated/app_localizations_ru.dart | 123 ++++++++++----- lib/l10n/generated/app_localizations_ta.dart | 138 +++++++++++------ lib/l10n/generated/app_localizations_th.dart | 96 ++++++++---- lib/l10n/generated/app_localizations_tl.dart | 141 +++++++++++------ lib/l10n/generated/app_localizations_tr.dart | 126 ++++++++++----- lib/l10n/generated/app_localizations_uk.dart | 117 +++++++++----- lib/l10n/generated/app_localizations_vi.dart | 129 ++++++++++------ lib/l10n/generated/app_localizations_zh.dart | 30 ++-- .../settings/metadata/metadata_form.dart | 11 +- .../audio_services/audio_services.dart | 10 +- pubspec.lock | 30 ++-- pubspec.yaml | 4 +- 39 files changed, 2327 insertions(+), 1165 deletions(-) diff --git a/.fvm/fvm_config.json b/.fvm/fvm_config.json index b4cb4720..1bcebbd2 100644 --- a/.fvm/fvm_config.json +++ b/.fvm/fvm_config.json @@ -1,3 +1,3 @@ { - "flutterSdkVersion": "3.29.2" + "flutterSdkVersion": "3.32.5" } \ No newline at end of file diff --git a/.fvmrc b/.fvmrc index 5b0ad692..4b100f8e 100644 --- a/.fvmrc +++ b/.fvmrc @@ -1,4 +1,4 @@ { - "flutter": "3.29.2", + "flutter": "3.32.5", "flavors": {} } \ No newline at end of file diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml index 31df69a7..ad162669 100644 --- a/.github/workflows/pr-lint.yml +++ b/.github/workflows/pr-lint.yml @@ -4,7 +4,7 @@ on: pull_request: env: - FLUTTER_VERSION: 3.29.2 + FLUTTER_VERSION: 3.32.5 jobs: lint: diff --git a/.github/workflows/spotube-release-binary.yml b/.github/workflows/spotube-release-binary.yml index 05a672d4..bebd8505 100644 --- a/.github/workflows/spotube-release-binary.yml +++ b/.github/workflows/spotube-release-binary.yml @@ -20,7 +20,7 @@ on: description: Dry run without uploading to release env: - FLUTTER_VERSION: 3.29.2 + FLUTTER_VERSION: 3.32.5 FLUTTER_CHANNEL: master permissions: diff --git a/.vscode/settings.json b/.vscode/settings.json index b848774e..a0829869 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -30,5 +30,5 @@ "README.md": "LICENSE,CODE_OF_CONDUCT.md,CONTRIBUTING.md,SECURITY.md,CONTRIBUTION.md,CHANGELOG.md,PRIVACY_POLICY.md", "*.dart": "${capture}.g.dart,${capture}.freezed.dart" }, - "dart.flutterSdkPath": ".fvm/versions/3.29.2" + "dart.flutterSdkPath": ".fvm/versions/3.32.5" } \ No newline at end of file diff --git a/lib/l10n/generated/app_localizations.dart b/lib/l10n/generated/app_localizations.dart index f0ada331..1758532a 100644 --- a/lib/l10n/generated/app_localizations.dart +++ b/lib/l10n/generated/app_localizations.dart @@ -89,7 +89,8 @@ import 'app_localizations_zh.dart'; /// be consistent with the languages listed in the AppLocalizations.supportedLocales /// property. abstract class AppLocalizations { - AppLocalizations(String locale) : localeName = intl.Intl.canonicalizedLocale(locale.toString()); + AppLocalizations(String locale) + : localeName = intl.Intl.canonicalizedLocale(locale.toString()); final String localeName; @@ -97,7 +98,8 @@ abstract class AppLocalizations { return Localizations.of(context, AppLocalizations); } - static const LocalizationsDelegate delegate = _AppLocalizationsDelegate(); + static const LocalizationsDelegate delegate = + _AppLocalizationsDelegate(); /// A list of this localizations delegate along with the default localizations /// delegates. @@ -109,7 +111,8 @@ abstract class AppLocalizations { /// Additional delegates can be added by appending to this list in /// MaterialApp. This list does not have to be used at all if a custom list /// of delegates is preferred or required. - static const List> localizationsDelegates = >[ + static const List> localizationsDelegates = + >[ delegate, GlobalMaterialLocalizations.delegate, GlobalCupertinoLocalizations.delegate, @@ -2652,7 +2655,8 @@ abstract class AppLocalizations { String get connection_request_denied; } -class _AppLocalizationsDelegate extends LocalizationsDelegate { +class _AppLocalizationsDelegate + extends LocalizationsDelegate { const _AppLocalizationsDelegate(); @override @@ -2661,52 +2665,108 @@ class _AppLocalizationsDelegate extends LocalizationsDelegate } @override - bool isSupported(Locale locale) => ['ar', 'bn', 'ca', 'cs', 'de', 'en', 'es', 'eu', 'fa', 'fi', 'fr', 'hi', 'id', 'it', 'ja', 'ka', 'ko', 'ne', 'nl', 'pl', 'pt', 'ru', 'ta', 'th', 'tl', 'tr', 'uk', 'vi', 'zh'].contains(locale.languageCode); + bool isSupported(Locale locale) => [ + 'ar', + 'bn', + 'ca', + 'cs', + 'de', + 'en', + 'es', + 'eu', + 'fa', + 'fi', + 'fr', + 'hi', + 'id', + 'it', + 'ja', + 'ka', + 'ko', + 'ne', + 'nl', + 'pl', + 'pt', + 'ru', + 'ta', + 'th', + 'tl', + 'tr', + 'uk', + 'vi', + 'zh' + ].contains(locale.languageCode); @override bool shouldReload(_AppLocalizationsDelegate old) => false; } AppLocalizations lookupAppLocalizations(Locale locale) { - - // Lookup logic when only language code is specified. switch (locale.languageCode) { - case 'ar': return AppLocalizationsAr(); - case 'bn': return AppLocalizationsBn(); - case 'ca': return AppLocalizationsCa(); - case 'cs': return AppLocalizationsCs(); - case 'de': return AppLocalizationsDe(); - case 'en': return AppLocalizationsEn(); - case 'es': return AppLocalizationsEs(); - case 'eu': return AppLocalizationsEu(); - case 'fa': return AppLocalizationsFa(); - case 'fi': return AppLocalizationsFi(); - case 'fr': return AppLocalizationsFr(); - case 'hi': return AppLocalizationsHi(); - case 'id': return AppLocalizationsId(); - case 'it': return AppLocalizationsIt(); - case 'ja': return AppLocalizationsJa(); - case 'ka': return AppLocalizationsKa(); - case 'ko': return AppLocalizationsKo(); - case 'ne': return AppLocalizationsNe(); - case 'nl': return AppLocalizationsNl(); - case 'pl': return AppLocalizationsPl(); - case 'pt': return AppLocalizationsPt(); - case 'ru': return AppLocalizationsRu(); - case 'ta': return AppLocalizationsTa(); - case 'th': return AppLocalizationsTh(); - case 'tl': return AppLocalizationsTl(); - case 'tr': return AppLocalizationsTr(); - case 'uk': return AppLocalizationsUk(); - case 'vi': return AppLocalizationsVi(); - case 'zh': return AppLocalizationsZh(); + case 'ar': + return AppLocalizationsAr(); + case 'bn': + return AppLocalizationsBn(); + case 'ca': + return AppLocalizationsCa(); + case 'cs': + return AppLocalizationsCs(); + case 'de': + return AppLocalizationsDe(); + case 'en': + return AppLocalizationsEn(); + case 'es': + return AppLocalizationsEs(); + case 'eu': + return AppLocalizationsEu(); + case 'fa': + return AppLocalizationsFa(); + case 'fi': + return AppLocalizationsFi(); + case 'fr': + return AppLocalizationsFr(); + case 'hi': + return AppLocalizationsHi(); + case 'id': + return AppLocalizationsId(); + case 'it': + return AppLocalizationsIt(); + case 'ja': + return AppLocalizationsJa(); + case 'ka': + return AppLocalizationsKa(); + case 'ko': + return AppLocalizationsKo(); + case 'ne': + return AppLocalizationsNe(); + case 'nl': + return AppLocalizationsNl(); + case 'pl': + return AppLocalizationsPl(); + case 'pt': + return AppLocalizationsPt(); + case 'ru': + return AppLocalizationsRu(); + case 'ta': + return AppLocalizationsTa(); + case 'th': + return AppLocalizationsTh(); + case 'tl': + return AppLocalizationsTl(); + case 'tr': + return AppLocalizationsTr(); + case 'uk': + return AppLocalizationsUk(); + case 'vi': + return AppLocalizationsVi(); + case 'zh': + return AppLocalizationsZh(); } throw FlutterError( - 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' - 'an issue with the localizations generation tool. Please file an issue ' - 'on GitHub with a reproducible sample app and the gen-l10n configuration ' - 'that was used.' - ); + 'AppLocalizations.delegate failed to load unsupported locale "$locale". This is likely ' + 'an issue with the localizations generation tool. Please file an issue ' + 'on GitHub with a reproducible sample app and the gen-l10n configuration ' + 'that was used.'); } diff --git a/lib/l10n/generated/app_localizations_ar.dart b/lib/l10n/generated/app_localizations_ar.dart index d5ea314f..362ab69d 100644 --- a/lib/l10n/generated/app_localizations_ar.dart +++ b/lib/l10n/generated/app_localizations_ar.dart @@ -407,7 +407,8 @@ class AppLocalizationsAr extends AppLocalizations { String get layout_mode => 'وضع التخطيط'; @override - String get override_layout_settings => 'تجاوز إعدادات وضع التخطيط سريع الاستجابة'; + String get override_layout_settings => + 'تجاوز إعدادات وضع التخطيط سريع الاستجابة'; @override String get adaptive => 'متكيف'; @@ -437,7 +438,8 @@ class AppLocalizationsAr extends AppLocalizations { String get sync_album_color => 'مزامنة لون الألبوم'; @override - String get sync_album_color_description => 'يستخدم اللون السائد لصورة الألبوم باعتباره لون التمييز'; + String get sync_album_color_description => + 'يستخدم اللون السائد لصورة الألبوم باعتباره لون التمييز'; @override String get playback => 'التشغيل'; @@ -455,16 +457,19 @@ class AppLocalizationsAr extends AppLocalizations { String get pre_download_play => 'التحميل المسبق والتشغيل'; @override - String get pre_download_play_description => 'بدلاً من دفق الصوت، قم بتنزيل وحدات البايت وتشغيلها بدلاً من ذلك (موصى به لمستخدمي Bandwidth)'; + String get pre_download_play_description => + 'بدلاً من دفق الصوت، قم بتنزيل وحدات البايت وتشغيلها بدلاً من ذلك (موصى به لمستخدمي Bandwidth)'; @override String get skip_non_music => 'تخطي المقاطع غير الموسيقية (SponsorBlock)'; @override - String get blacklist_description => 'المقطوعات والفنانون المدرجون في القائمة السوداء'; + String get blacklist_description => + 'المقطوعات والفنانون المدرجون في القائمة السوداء'; @override - String get wait_for_download_to_finish => 'يرجى الانتظار حتى انتهاء التنزيل الحالي'; + String get wait_for_download_to_finish => + 'يرجى الانتظار حتى انتهاء التنزيل الحالي'; @override String get desktop => 'سطح المكتب'; @@ -500,7 +505,8 @@ class AppLocalizationsAr extends AppLocalizations { String get please_sponsor => 'يرجى دعم/التبرع'; @override - String get spotube_description => 'Spotube، عميل Spotify خفيف الوزن ومتعدد المنصات ومجاني للجميع'; + String get spotube_description => + 'Spotube، عميل Spotify خفيف الوزن ومتعدد المنصات ومجاني للجميع'; @override String get version => 'إصدار'; @@ -532,7 +538,8 @@ class AppLocalizationsAr extends AppLocalizations { String get license => 'الترخيص'; @override - String get credentials_will_not_be_shared_disclaimer => 'لا تقلق، لن يتم جمع أي من بيانات الخاصة بك أو مشاركتها مع أي شخص'; + String get credentials_will_not_be_shared_disclaimer => + 'لا تقلق، لن يتم جمع أي من بيانات الخاصة بك أو مشاركتها مع أي شخص'; @override String get know_how_to_login => 'لا تعرف كيف تفعل هذا؟'; @@ -576,19 +583,23 @@ class AppLocalizationsAr extends AppLocalizations { String get piped_instance => 'مثيل خادم Piped'; @override - String get piped_description => 'مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة'; + String get piped_description => + 'مثيل خادم Piped الذي سيتم استخدامه لمطابقة المقطوعة'; @override - String get piped_warning => 'البعض منهم قد لا يعمل بشكل جيد. لذلك استخدمه على مسؤوليتك'; + String get piped_warning => + 'البعض منهم قد لا يعمل بشكل جيد. لذلك استخدمه على مسؤوليتك'; @override String get invidious_instance => 'مثيل خادم Invidious'; @override - String get invidious_description => 'مثيل خادم Invidious المستخدم لمطابقة المسارات'; + String get invidious_description => + 'مثيل خادم Invidious المستخدم لمطابقة المسارات'; @override - String get invidious_warning => 'قد لا تعمل بعض الخوادم بشكل جيد. استخدمها على مسؤوليتك الخاصة'; + String get invidious_warning => + 'قد لا تعمل بعض الخوادم بشكل جيد. استخدمها على مسؤوليتك الخاصة'; @override String get generate => 'إنشاء'; @@ -599,10 +610,12 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'استبدل جميع المقطوعات التي تم تنزيلها'; + String get replace_downloaded_tracks => + 'استبدل جميع المقطوعات التي تم تنزيلها'; @override - String get skip_download_tracks => 'تخطي تنزيل كافة المقطوعات التي تم تنزيلها'; + String get skip_download_tracks => + 'تخطي تنزيل كافة المقطوعات التي تم تنزيلها'; @override String get do_you_want_to_replace => 'هل تريد استبدال المقطوعة الحالية؟'; @@ -628,7 +641,8 @@ class AppLocalizationsAr extends AppLocalizations { String get country => 'دولة'; @override - String get number_of_tracks_generate => 'عدد المسارات المقطوعات المراد توليدها'; + String get number_of_tracks_generate => + 'عدد المسارات المقطوعات المراد توليدها'; @override String get acousticness => 'صوتية'; @@ -711,22 +725,27 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get download_warning => 'إذا قمت بتنزيل جميع المقاطع الصوتية بكميات كبيرة، فمن الواضح أنك تقوم بقرصنة الموسيقى وتسبب الضرر للمجتمع الإبداعي للموسيقى. أتمنى أن تكون على علم بهذا. حاول دائمًا احترام ودعم العمل الجاد للفنان'; + String get download_warning => + 'إذا قمت بتنزيل جميع المقاطع الصوتية بكميات كبيرة، فمن الواضح أنك تقوم بقرصنة الموسيقى وتسبب الضرر للمجتمع الإبداعي للموسيقى. أتمنى أن تكون على علم بهذا. حاول دائمًا احترام ودعم العمل الجاد للفنان'; @override - String get download_ip_ban_warning => 'بالمناسبة، يمكن أن يتم حظر عنوان IP الخاص بك على YouTube بسبب طلبات التنزيل الزائدة عن المعتاد. يعني حظر IP أنه لا يمكنك استخدام YouTube (حتى إذا قمت بتسجيل الدخول) لمدة تتراوح بين شهرين إلى ثلاثة أشهر على الأقل من جهاز IP هذا. ولا يتحمل Spotube أي مسؤولية إذا حدث هذا على الإطلاق'; + String get download_ip_ban_warning => + 'بالمناسبة، يمكن أن يتم حظر عنوان IP الخاص بك على YouTube بسبب طلبات التنزيل الزائدة عن المعتاد. يعني حظر IP أنه لا يمكنك استخدام YouTube (حتى إذا قمت بتسجيل الدخول) لمدة تتراوح بين شهرين إلى ثلاثة أشهر على الأقل من جهاز IP هذا. ولا يتحمل Spotube أي مسؤولية إذا حدث هذا على الإطلاق'; @override - String get by_clicking_accept_terms => 'بالنقر على \"قبول\"، فإنك توافق على الشروط التالية:'; + String get by_clicking_accept_terms => + 'بالنقر على \"قبول\"، فإنك توافق على الشروط التالية:'; @override String get download_agreement_1 => 'أعلم أنني أقوم بقرصنة الموسيقى. انا سيئ'; @override - String get download_agreement_2 => 'سأدعم الفنان أينما أستطيع، وأنا أفعل هذا فقط لأنني لا أملك المال لشراء أعمالهم الفنية'; + String get download_agreement_2 => + 'سأدعم الفنان أينما أستطيع، وأنا أفعل هذا فقط لأنني لا أملك المال لشراء أعمالهم الفنية'; @override - String get download_agreement_3 => 'أدرك تمامًا أنه يمكن حظر عنوان IP الخاص بي على YouTube ولا أحمل Spotube أو مالكيه/مساهميه المسؤولية عن أي حوادث ناجمة عن الإجراء الحالي الخاص بي'; + String get download_agreement_3 => + 'أدرك تمامًا أنه يمكن حظر عنوان IP الخاص بي على YouTube ولا أحمل Spotube أو مالكيه/مساهميه المسؤولية عن أي حوادث ناجمة عن الإجراء الحالي الخاص بي'; @override String get decline => 'رفض'; @@ -807,7 +826,8 @@ class AppLocalizationsAr extends AppLocalizations { String get failed_to_encrypt => 'فشل في التشفير'; @override - String get encryption_failed_warning => 'يستخدم Spotube التشفير لتخزين بياناتك بشكل آمن. لكنها فشلت في القيام بذلك. لذلك سيعود الأمر إلى التخزين غير الآمن\nإذا كنت تستخدم Linux، فيرجى التأكد من تثبيت أي خدمة سرية (gnome-keyring، kde-wallet، keepassxc، إلخ)'; + String get encryption_failed_warning => + 'يستخدم Spotube التشفير لتخزين بياناتك بشكل آمن. لكنها فشلت في القيام بذلك. لذلك سيعود الأمر إلى التخزين غير الآمن\nإذا كنت تستخدم Linux، فيرجى التأكد من تثبيت أي خدمة سرية (gnome-keyring، kde-wallet، keepassxc، إلخ)'; @override String get querying_info => 'جارٍ الاستعلام عن معلومات...'; @@ -881,7 +901,8 @@ class AppLocalizationsAr extends AppLocalizations { String get login => 'تسجيل الدخول'; @override - String get login_with_your_lastfm => 'تسجيل الدخول باستخدام حساب Last.fm الخاص بك'; + String get login_with_your_lastfm => + 'تسجيل الدخول باستخدام حساب Last.fm الخاص بك'; @override String get scrobble_to_lastfm => 'تسجيل الاستماع على Last.fm'; @@ -905,7 +926,8 @@ class AppLocalizationsAr extends AppLocalizations { String get friends => 'أصدقاء'; @override - String get no_lyrics_available => 'عذرًا، تعذر العثور على كلمات الأغنية لهذه العنصر'; + String get no_lyrics_available => + 'عذرًا، تعذر العثور على كلمات الأغنية لهذه العنصر'; @override String get start_a_radio => 'بدء راديو'; @@ -914,7 +936,8 @@ class AppLocalizationsAr extends AppLocalizations { String get how_to_start_radio => 'كيف تريد بدء الراديو؟'; @override - String get replace_queue_question => 'هل تريد استبدال قائمة التشغيل الحالية أم إضافة إليها؟'; + String get replace_queue_question => + 'هل تريد استبدال قائمة التشغيل الحالية أم إضافة إليها؟'; @override String get endless_playback => 'تشغيل بلا نهاية'; @@ -923,7 +946,8 @@ class AppLocalizationsAr extends AppLocalizations { String get delete_playlist => 'حذف قائمة التشغيل'; @override - String get delete_playlist_confirmation => 'هل أنت متأكد أنك تريد حذف هذه قائمة التشغيل؟'; + String get delete_playlist_confirmation => + 'هل أنت متأكد أنك تريد حذف هذه قائمة التشغيل؟'; @override String get local_tracks => 'المسارات المحلية'; @@ -950,7 +974,8 @@ class AppLocalizationsAr extends AppLocalizations { String get youtube_source_description => 'موصى به ويعمل بشكل أفضل.'; @override - String get piped_source_description => 'تشعر بالحرية؟ نفس يوتيوب ولكن أكثر حرية.'; + String get piped_source_description => + 'تشعر بالحرية؟ نفس يوتيوب ولكن أكثر حرية.'; @override String get jiosaavn_source_description => 'الأفضل لمنطقة جنوب آسيا.'; @@ -967,13 +992,15 @@ class AppLocalizationsAr extends AppLocalizations { String get select_audio_source => 'اختر مصدر الصوت'; @override - String get endless_playback_description => 'إلحاق الأغاني الجديدة تلقائيًا\nإلى نهاية قائمة التشغيل'; + String get endless_playback_description => + 'إلحاق الأغاني الجديدة تلقائيًا\nإلى نهاية قائمة التشغيل'; @override String get choose_your_region => 'اختر منطقتك'; @override - String get choose_your_region_description => 'سيساعدك هذا في عرض المحتوى المناسب\nلموقعك.'; + String get choose_your_region_description => + 'سيساعدك هذا في عرض المحتوى المناسب\nلموقعك.'; @override String get choose_your_language => 'اختر لغتك'; @@ -982,7 +1009,8 @@ class AppLocalizationsAr extends AppLocalizations { String get help_project_grow => 'ساعد في نمو هذا المشروع'; @override - String get help_project_grow_description => 'Spotube هو مشروع مفتوح المصدر. يمكنك مساعدة هذا المشروع في النمو عن طريق المساهمة في المشروع، أو الإبلاغ عن الأخطاء، أو اقتراح ميزات جديدة.'; + String get help_project_grow_description => + 'Spotube هو مشروع مفتوح المصدر. يمكنك مساعدة هذا المشروع في النمو عن طريق المساهمة في المشروع، أو الإبلاغ عن الأخطاء، أو اقتراح ميزات جديدة.'; @override String get contribute_on_github => 'المساهمة على GitHub'; @@ -997,7 +1025,8 @@ class AppLocalizationsAr extends AppLocalizations { String get enable_connect => 'تمكين الاتصال'; @override - String get enable_connect_description => 'التحكم في Spotube من الأجهزة الأخرى'; + String get enable_connect_description => + 'التحكم في Spotube من الأجهزة الأخرى'; @override String get devices => 'الأجهزة'; @@ -1076,7 +1105,8 @@ class AppLocalizationsAr extends AppLocalizations { String get choose_the_device => 'اختر الجهاز:'; @override - String get multiple_device_connected => 'تم توصيل أجهزة متعددة.\nاختر الجهاز الذي تريد إجراء هذه العملية عليه'; + String get multiple_device_connected => + 'تم توصيل أجهزة متعددة.\nاختر الجهاز الذي تريد إجراء هذه العملية عليه'; @override String get nothing_found => 'لم يتم العثور على شيء'; @@ -1171,7 +1201,8 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1251,8 @@ class AppLocalizationsAr extends AppLocalizations { String get webview_not_found => 'لم يتم العثور على Webview'; @override - String get webview_not_found_description => 'لم يتم تثبيت بيئة تشغيل Webview على جهازك.\nإذا كانت مثبتة، تأكد من وجودها في environment PATH\n\nبعد التثبيت، أعد تشغيل التطبيق'; + String get webview_not_found_description => + 'لم يتم تثبيت بيئة تشغيل Webview على جهازك.\nإذا كانت مثبتة، تأكد من وجودها في environment PATH\n\nبعد التثبيت، أعد تشغيل التطبيق'; @override String get unsupported_platform => 'المنصة غير مدعومة'; @@ -1293,7 +1325,8 @@ class AppLocalizationsAr extends AppLocalizations { String get not_following_artists => 'أنت لا تتابع أي فنانين'; @override - String get no_favorite_albums_yet => 'يبدو أنك لم تضف أي ألبومات إلى المفضلة بعد'; + String get no_favorite_albums_yet => + 'يبدو أنك لم تضف أي ألبومات إلى المفضلة بعد'; @override String get no_logs_found => 'لم يتم العثور على سجلات'; @@ -1317,7 +1350,8 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'في أنظمة macOS/Linux/Unix مثل الأنظمة، لن يعمل تعيين المسار في .zshrc/.bashrc/.bash_profile وما إلى ذلك.\nيجب تعيين المسار في ملف تكوين الصدفة'; + String get youtube_engine_unix_issue_message => + 'في أنظمة macOS/Linux/Unix مثل الأنظمة، لن يعمل تعيين المسار في .zshrc/.bashrc/.bash_profile وما إلى ذلك.\nيجب تعيين المسار في ملف تكوين الصدفة'; @override String get download => 'تنزيل'; @@ -1335,7 +1369,8 @@ class AppLocalizationsAr extends AppLocalizations { String get edit_port => 'تعديل المنفذ'; @override - String get port_helper_msg => 'القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.'; + String get port_helper_msg => + 'القيمة الافتراضية هي -1 والتي تشير إلى رقم عشوائي. إذا كان لديك جدار ناري مُعد، يُوصى بتعيين هذا.'; @override String connect_request(Object client) { @@ -1343,5 +1378,6 @@ class AppLocalizationsAr extends AppLocalizations { } @override - String get connection_request_denied => 'تم رفض الاتصال. المستخدم رفض الوصول.'; + String get connection_request_denied => + 'تم رفض الاتصال. المستخدم رفض الوصول.'; } diff --git a/lib/l10n/generated/app_localizations_bn.dart b/lib/l10n/generated/app_localizations_bn.dart index 575f1c6c..1014254a 100644 --- a/lib/l10n/generated/app_localizations_bn.dart +++ b/lib/l10n/generated/app_localizations_bn.dart @@ -407,7 +407,8 @@ class AppLocalizationsBn extends AppLocalizations { String get layout_mode => 'UI বিন্যাস রূপ'; @override - String get override_layout_settings => 'প্রতিক্রিয়াশীল UI বিন্যাস রূপের সেটিংস পরিবর্তন করুন'; + String get override_layout_settings => + 'প্রতিক্রিয়াশীল UI বিন্যাস রূপের সেটিংস পরিবর্তন করুন'; @override String get adaptive => 'অভিযোজিত'; @@ -437,7 +438,8 @@ class AppLocalizationsBn extends AppLocalizations { String get sync_album_color => 'অ্যালবাম সুসংগত UI এর রং'; @override - String get sync_album_color_description => 'অ্যালবাম কভারের প্রভাবশালী রঙ UI অ্যাকসেন্ট রঙ হিসাবে ব্যবহার করে'; + String get sync_album_color_description => + 'অ্যালবাম কভারের প্রভাবশালী রঙ UI অ্যাকসেন্ট রঙ হিসাবে ব্যবহার করে'; @override String get playback => 'সংগীতের প্লেব্যাক'; @@ -455,16 +457,20 @@ class AppLocalizationsBn extends AppLocalizations { String get pre_download_play => 'আগে গান ডাউনলোড করে পরে চালান '; @override - String get pre_download_play_description => 'গান স্ট্রিম করার পরিবর্তে, ডাউনলোড করুন এবং প্লে করুন (উচ্চ ব্যান্ডউইথ ব্যবহারকারীদের জন্য প্রস্তাবিত)'; + String get pre_download_play_description => + 'গান স্ট্রিম করার পরিবর্তে, ডাউনলোড করুন এবং প্লে করুন (উচ্চ ব্যান্ডউইথ ব্যবহারকারীদের জন্য প্রস্তাবিত)'; @override - String get skip_non_music => 'গানের নন-মিউজিক সেগমেন্ট এড়িয়ে যান (SponsorBlock)'; + String get skip_non_music => + 'গানের নন-মিউজিক সেগমেন্ট এড়িয়ে যান (SponsorBlock)'; @override - String get blacklist_description => 'কালো তালিকাভুক্ত গানের ট্র্যাক এবং শিল্পী'; + String get blacklist_description => + 'কালো তালিকাভুক্ত গানের ট্র্যাক এবং শিল্পী'; @override - String get wait_for_download_to_finish => 'ডাউনলোড শেষ হওয়ার জন্য অপেক্ষা করুন'; + String get wait_for_download_to_finish => + 'ডাউনলোড শেষ হওয়ার জন্য অপেক্ষা করুন'; @override String get desktop => 'ডেস্কটপ'; @@ -500,7 +506,8 @@ class AppLocalizationsBn extends AppLocalizations { String get please_sponsor => 'স্পনসর/সহায়তা করুন'; @override - String get spotube_description => 'Spotube, একটি কর্মদক্ষ, ক্রস-প্ল্যাটফর্ম, বিনামূল্যের জন্য Spotify ক্লায়েন্ট'; + String get spotube_description => + 'Spotube, একটি কর্মদক্ষ, ক্রস-প্ল্যাটফর্ম, বিনামূল্যের জন্য Spotify ক্লায়েন্ট'; @override String get version => 'সংস্করণ'; @@ -532,7 +539,8 @@ class AppLocalizationsBn extends AppLocalizations { String get license => 'লাইসেন্স'; @override - String get credentials_will_not_be_shared_disclaimer => 'চিন্তা করবেন না, আপনার কোনো লগইন তথ্য সংগ্রহ করা হবে না বা কারো সাথে শেয়ার করা হবে না'; + String get credentials_will_not_be_shared_disclaimer => + 'চিন্তা করবেন না, আপনার কোনো লগইন তথ্য সংগ্রহ করা হবে না বা কারো সাথে শেয়ার করা হবে না'; @override String get know_how_to_login => 'আপনি কিভাবে লগইন করবেন তা জানেন না?'; @@ -579,16 +587,19 @@ class AppLocalizationsBn extends AppLocalizations { String get piped_description => 'গান ম্যাচ করার জন্য ব্যবহৃত পাইপড সার্ভার'; @override - String get piped_warning => 'এগুলোর মধ্যে কিছু ভাল কাজ নাও করতে পারে৷ তাই নিজ দায়িত্বে ব্যবহার করুন'; + String get piped_warning => + 'এগুলোর মধ্যে কিছু ভাল কাজ নাও করতে পারে৷ তাই নিজ দায়িত্বে ব্যবহার করুন'; @override String get invidious_instance => 'ইনভিডিয়াস সার্ভার ইন্সটেন্স'; @override - String get invidious_description => 'ট্রাক মিলানোর জন্য ব্যবহৃত ইনভিডিয়াস সার্ভার'; + String get invidious_description => + 'ট্রাক মিলানোর জন্য ব্যবহৃত ইনভিডিয়াস সার্ভার'; @override - String get invidious_warning => 'কিছু সার্ভার ভাল কাজ নাও করতে পারে। নিজের ঝুঁকিতে ব্যবহার করুন'; + String get invidious_warning => + 'কিছু সার্ভার ভাল কাজ নাও করতে পারে। নিজের ঝুঁকিতে ব্যবহার করুন'; @override String get generate => 'উৎপন্ন করুন'; @@ -599,13 +610,15 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'সমস্ত ডাউনলোড করা ট্র্যাক প্রতিস্থাপন করুন'; + String get replace_downloaded_tracks => + 'সমস্ত ডাউনলোড করা ট্র্যাক প্রতিস্থাপন করুন'; @override String get skip_download_tracks => 'সমস্ত ডাউনলোড করা ট্র্যাক এ স্কিপ করুন'; @override - String get do_you_want_to_replace => 'আপনি কি বিদ্যমান ট্র্যাকটি প্রতিস্থাপন করতে চান?'; + String get do_you_want_to_replace => + 'আপনি কি বিদ্যমান ট্র্যাকটি প্রতিস্থাপন করতে চান?'; @override String get replace => 'প্রতিস্থাপন করুন'; @@ -711,22 +724,27 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get download_warning => 'যদি আপনি সমস্ত ট্র্যাকগুলি একসঙ্গে ডাউনলোড করেন, তবে আপনি নিশ্চিতভাবে সঙ্গীত চুরি করছেন এবং সৃষ্টিশীল সমাজে ক্ষতি দিচ্ছেন। আমি আশা করি আপনি এটা সম্পর্কে জানেন। সর্বদা, শিল্পীদের কঠিন পরিশ্রমকে সম্মান করতে চেষ্টা করুন এবং সমর্থন করুন'; + String get download_warning => + 'যদি আপনি সমস্ত ট্র্যাকগুলি একসঙ্গে ডাউনলোড করেন, তবে আপনি নিশ্চিতভাবে সঙ্গীত চুরি করছেন এবং সৃষ্টিশীল সমাজে ক্ষতি দিচ্ছেন। আমি আশা করি আপনি এটা সম্পর্কে জানেন। সর্বদা, শিল্পীদের কঠিন পরিশ্রমকে সম্মান করতে চেষ্টা করুন এবং সমর্থন করুন'; @override - String get download_ip_ban_warning => 'তথ্যবিশ্বস্ত করে নেওয়া যায় যে, আপনার IP ঠিকানাটি YouTube দ্বারা স্থানান্তরিত করা হতে পারে যখন সাধারন থেকে বেশি ডাউনলোড অনুরোধ হয়। IP ব্লকের মাধ্যমে আপনি কমপক্ষে ২-৩ মাস ধরে (ঐ IP ডিভাইস থেকে) YouTube ব্যবহার করতে পারবেন না। এবং Spotube কোনও দায়িত্ব সম্পর্কে দায়িত্ব বহন করে না যদি এটি ঘটে।'; + String get download_ip_ban_warning => + 'তথ্যবিশ্বস্ত করে নেওয়া যায় যে, আপনার IP ঠিকানাটি YouTube দ্বারা স্থানান্তরিত করা হতে পারে যখন সাধারন থেকে বেশি ডাউনলোড অনুরোধ হয়। IP ব্লকের মাধ্যমে আপনি কমপক্ষে ২-৩ মাস ধরে (ঐ IP ডিভাইস থেকে) YouTube ব্যবহার করতে পারবেন না। এবং Spotube কোনও দায়িত্ব সম্পর্কে দায়িত্ব বহন করে না যদি এটি ঘটে।'; @override - String get by_clicking_accept_terms => '\'গ্রহণ\' ক্লিক করে আপনি নিম্নলিখিত শর্তাদি স্বীকার করছেন:'; + String get by_clicking_accept_terms => + '\'গ্রহণ\' ক্লিক করে আপনি নিম্নলিখিত শর্তাদি স্বীকার করছেন:'; @override String get download_agreement_1 => 'আমি জানি আমি সঙ্গীত চুরি করছি। আমি খারাপ'; @override - String get download_agreement_2 => 'আমি কেবলমাত্র তাদের কাজ কেনার জন্য অর্থ নেই কিন্তু যেখানে প্রয়োজন সেখানে আমি শিল্পীদের সমর্থন করব।'; + String get download_agreement_2 => + 'আমি কেবলমাত্র তাদের কাজ কেনার জন্য অর্থ নেই কিন্তু যেখানে প্রয়োজন সেখানে আমি শিল্পীদের সমর্থন করব।'; @override - String get download_agreement_3 => 'আমি সম্পূর্ণরূপে জানি যে আমার IP YouTube-তে ব্লক হতে পারে এবং আমি Spotube বা তার মালিকানাধীন কোনও দায়িত্ব পেতে পারিনি আমার বর্তমান ক্রিয়াটি দ্বারা সৃষ্ট দুর্ঘটনা করার জন্য'; + String get download_agreement_3 => + 'আমি সম্পূর্ণরূপে জানি যে আমার IP YouTube-তে ব্লক হতে পারে এবং আমি Spotube বা তার মালিকানাধীন কোনও দায়িত্ব পেতে পারিনি আমার বর্তমান ক্রিয়াটি দ্বারা সৃষ্ট দুর্ঘটনা করার জন্য'; @override String get decline => 'অগ্রায়ন করুন'; @@ -807,7 +825,8 @@ class AppLocalizationsBn extends AppLocalizations { String get failed_to_encrypt => 'এনক্রিপ্ট করা ব্যর্থ হয়েছে'; @override - String get encryption_failed_warning => 'Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে'; + String get encryption_failed_warning => + 'Spotube আপনার তথ্যগুলি নিরাপদভাবে স্টোর করতে এনক্রিপশন ব্যবহার করে। কিন্তু এটি ব্যর্থ হয়েছে। তাই এটি অনিরাপদ স্টোরে ফলফল হবে\nযদি আপনি Linux ব্যবহার করেন, তবে দয়া করে নিশ্চিত হউন যে আপনার কোনও সিক্রেট-সার্ভিস gnome-keyring, kde-wallet, keepassxc ইত্যাদি ইনস্টল করা আছে'; @override String get querying_info => 'তথ্য অনুসন্ধান করা হচ্ছে'; @@ -881,7 +900,8 @@ class AppLocalizationsBn extends AppLocalizations { String get login => 'লগইন'; @override - String get login_with_your_lastfm => 'আপনার Last.fm অ্যাকাউন্ট দিয়ে লগইন করুন'; + String get login_with_your_lastfm => + 'আপনার Last.fm অ্যাকাউন্ট দিয়ে লগইন করুন'; @override String get scrobble_to_lastfm => 'Last.fm এ স্ক্রবল করুন'; @@ -905,7 +925,8 @@ class AppLocalizationsBn extends AppLocalizations { String get friends => 'বন্ধু'; @override - String get no_lyrics_available => 'দুঃখিত, এই ট্র্যাকের জন্য কথা খুঁজে পাওয়া গেলনা'; + String get no_lyrics_available => + 'দুঃখিত, এই ট্র্যাকের জন্য কথা খুঁজে পাওয়া গেলনা'; @override String get start_a_radio => 'রেডিও শুরু করুন'; @@ -914,7 +935,8 @@ class AppLocalizationsBn extends AppLocalizations { String get how_to_start_radio => 'রেডিও কিভাবে শুরু করতে চান?'; @override - String get replace_queue_question => 'আপনি বর্তমান কিউটি প্রতিস্থাপন করতে চান কিনা বা এর সাথে যুক্ত করতে চান?'; + String get replace_queue_question => + 'আপনি বর্তমান কিউটি প্রতিস্থাপন করতে চান কিনা বা এর সাথে যুক্ত করতে চান?'; @override String get endless_playback => 'অবিরাম প্রচার'; @@ -923,7 +945,8 @@ class AppLocalizationsBn extends AppLocalizations { String get delete_playlist => 'প্লেলিস্ট মুছুন'; @override - String get delete_playlist_confirmation => 'আপনি কি নিশ্চিত যে আপনি এই প্লেলিস্টটি মুছতে চান?'; + String get delete_playlist_confirmation => + 'আপনি কি নিশ্চিত যে আপনি এই প্লেলিস্টটি মুছতে চান?'; @override String get local_tracks => 'স্থানীয় ট্র্যাক'; @@ -956,7 +979,8 @@ class AppLocalizationsBn extends AppLocalizations { String get jiosaavn_source_description => 'দক্ষিণ এশিয়ান অঞ্চলের জন্য সেরা।'; @override - String get invidious_source_description => 'পাইপের মতো কিন্তু আরও বেশি উপলব্ধতা সহ'; + String get invidious_source_description => + 'পাইপের মতো কিন্তু আরও বেশি উপলব্ধতা সহ'; @override String highest_quality(Object quality) { @@ -967,13 +991,15 @@ class AppLocalizationsBn extends AppLocalizations { String get select_audio_source => 'অডিও উৎস নির্বাচন করুন'; @override - String get endless_playback_description => 'নতুন গান নিজে নিজে প্লেলিস্টের শেষে\nসংযুক্ত করুন'; + String get endless_playback_description => + 'নতুন গান নিজে নিজে প্লেলিস্টের শেষে\nসংযুক্ত করুন'; @override String get choose_your_region => 'আপনার অঞ্চল নির্বাচন করুন'; @override - String get choose_your_region_description => 'এটি স্পটুবে আপনাকে আপনার অবস্থানের জন্য ঠিক কন্টেন্ট দেখানোর সাহায্য করবে।'; + String get choose_your_region_description => + 'এটি স্পটুবে আপনাকে আপনার অবস্থানের জন্য ঠিক কন্টেন্ট দেখানোর সাহায্য করবে।'; @override String get choose_your_language => 'আপনার ভাষা নির্বাচন করুন'; @@ -982,7 +1008,8 @@ class AppLocalizationsBn extends AppLocalizations { String get help_project_grow => 'এই প্রকল্পের বৃদ্ধি করুন'; @override - String get help_project_grow_description => 'স্পটুব একটি ওপেন সোর্স প্রকল্প। আপনি প্রকল্পে অবদান রাখেন, বাগ রিপোর্ট করেন, বা নতুন বৈশিষ্ট্যগুলি সুপারিশ করেন।'; + String get help_project_grow_description => + 'স্পটুব একটি ওপেন সোর্স প্রকল্প। আপনি প্রকল্পে অবদান রাখেন, বাগ রিপোর্ট করেন, বা নতুন বৈশিষ্ট্যগুলি সুপারিশ করেন।'; @override String get contribute_on_github => 'গিটহাবে অবদান রাখুন'; @@ -997,7 +1024,8 @@ class AppLocalizationsBn extends AppLocalizations { String get enable_connect => 'সংযোগ সক্রিয় করুন'; @override - String get enable_connect_description => 'অন্যান্য ডিভাইস থেকে Spotube নিয়ন্ত্রণ করুন'; + String get enable_connect_description => + 'অন্যান্য ডিভাইস থেকে Spotube নিয়ন্ত্রণ করুন'; @override String get devices => 'ডিভাইস'; @@ -1076,7 +1104,8 @@ class AppLocalizationsBn extends AppLocalizations { String get choose_the_device => 'ডিভাইস নির্বাচন করুন:'; @override - String get multiple_device_connected => 'একাধিক ডিভাইস সংযুক্ত রয়েছে।\nযে ডিভাইসে আপনি এই ক্রিয়াটি চালাতে চান সেটি নির্বাচন করুন'; + String get multiple_device_connected => + 'একাধিক ডিভাইস সংযুক্ত রয়েছে।\nযে ডিভাইসে আপনি এই ক্রিয়াটি চালাতে চান সেটি নির্বাচন করুন'; @override String get nothing_found => 'কিছুই পাওয়া যায়নি'; @@ -1171,7 +1200,8 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1250,8 @@ class AppLocalizationsBn extends AppLocalizations { String get webview_not_found => 'ওয়েবভিউ পাওয়া যায়নি'; @override - String get webview_not_found_description => 'আপনার ডিভাইসে কোনো ওয়েবভিউ রানটাইম ইনস্টল করা নেই।\nযদি ইনস্টল থাকে, তা নিশ্চিত করুন যে এটি environment PATH এ রয়েছে\n\nইনস্টল করার পর, অ্যাপটি পুনরায় চালু করুন'; + String get webview_not_found_description => + 'আপনার ডিভাইসে কোনো ওয়েবভিউ রানটাইম ইনস্টল করা নেই।\nযদি ইনস্টল থাকে, তা নিশ্চিত করুন যে এটি environment PATH এ রয়েছে\n\nইনস্টল করার পর, অ্যাপটি পুনরায় চালু করুন'; @override String get unsupported_platform => 'সমর্থিত প্ল্যাটফর্ম নয়'; @@ -1252,7 +1283,8 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get export_cache_confirmation => 'আপনি কি এই ফাইলগুলি রপ্তানি করতে চান'; + String get export_cache_confirmation => + 'আপনি কি এই ফাইলগুলি রপ্তানি করতে চান'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1293,7 +1325,8 @@ class AppLocalizationsBn extends AppLocalizations { String get not_following_artists => 'আপনি কোনো শিল্পীকে অনুসরণ করছেন না'; @override - String get no_favorite_albums_yet => 'এখনও কোনো অ্যালবাম প্রিয় তালিকায় যোগ করা হয়নি মনে হচ্ছে'; + String get no_favorite_albums_yet => + 'এখনও কোনো অ্যালবাম প্রিয় তালিকায় যোগ করা হয়নি মনে হচ্ছে'; @override String get no_logs_found => 'কোনো লগ পাওয়া যায়নি'; @@ -1317,7 +1350,8 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/Unix-এর মতো অপারেটিং সিস্টেমে, .zshrc/.bashrc/.bash_profile ইত্যাদিতে পাথ সেট করা কাজ করবে না।\nআপনাকে শেল কনফিগারেশন ফাইলে পাথ সেট করতে হবে'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/Unix-এর মতো অপারেটিং সিস্টেমে, .zshrc/.bashrc/.bash_profile ইত্যাদিতে পাথ সেট করা কাজ করবে না।\nআপনাকে শেল কনফিগারেশন ফাইলে পাথ সেট করতে হবে'; @override String get download => 'ডাউনলোড'; @@ -1335,7 +1369,8 @@ class AppLocalizationsBn extends AppLocalizations { String get edit_port => 'পোর্ট সম্পাদনা করুন'; @override - String get port_helper_msg => 'ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।'; + String get port_helper_msg => + 'ডিফল্ট হল -1 যা এলোমেলো সংখ্যা নির্দেশ করে। যদি আপনার ফায়ারওয়াল কনফিগার করা থাকে, তবে এটি সেট করা সুপারিশ করা হয়।'; @override String connect_request(Object client) { @@ -1343,5 +1378,6 @@ class AppLocalizationsBn extends AppLocalizations { } @override - String get connection_request_denied => 'সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।'; + String get connection_request_denied => + 'সংযোগ অস্বীকৃত। ব্যবহারকারী প্রবেশাধিকার অস্বীকার করেছে।'; } diff --git a/lib/l10n/generated/app_localizations_ca.dart b/lib/l10n/generated/app_localizations_ca.dart index 00207f89..7069651f 100644 --- a/lib/l10n/generated/app_localizations_ca.dart +++ b/lib/l10n/generated/app_localizations_ca.dart @@ -356,7 +356,8 @@ class AppLocalizationsCa extends AppLocalizations { String get clear_all => 'Netejar tot'; @override - String get show_hide_ui_on_hover => 'Mostrar/Ocultar interfície al passar el cursor'; + String get show_hide_ui_on_hover => + 'Mostrar/Ocultar interfície al passar el cursor'; @override String get always_on_top => 'Sempre visible'; @@ -407,7 +408,8 @@ class AppLocalizationsCa extends AppLocalizations { String get layout_mode => 'Mode de disseny'; @override - String get override_layout_settings => 'Anul·leu la configuració del mode de disseny responsiu'; + String get override_layout_settings => + 'Anul·leu la configuració del mode de disseny responsiu'; @override String get adaptive => 'Adaptable'; @@ -437,7 +439,8 @@ class AppLocalizationsCa extends AppLocalizations { String get sync_album_color => 'Sincronitzar color de l\'àlbum'; @override - String get sync_album_color_description => 'Utilitza el color dominant de l\'álbum com a color d\'accent'; + String get sync_album_color_description => + 'Utilitza el color dominant de l\'álbum com a color d\'accent'; @override String get playback => 'Reproducció'; @@ -455,16 +458,19 @@ class AppLocalizationsCa extends AppLocalizations { String get pre_download_play => 'Descàrrega prèvia i reproduir'; @override - String get pre_download_play_description => 'En lloc de transmetre l\'àudio, descarrega bytes i ho reprodueix (recomendat per usuaris amb un bon ample de banda)'; + String get pre_download_play_description => + 'En lloc de transmetre l\'àudio, descarrega bytes i ho reprodueix (recomendat per usuaris amb un bon ample de banda)'; @override - String get skip_non_music => 'Ometre segments que no son música (SponsorBlock)'; + String get skip_non_music => + 'Ometre segments que no son música (SponsorBlock)'; @override String get blacklist_description => 'Cançons i artistes de la llista negra'; @override - String get wait_for_download_to_finish => 'Si us plau, esperi que acabi la descàrrega actual'; + String get wait_for_download_to_finish => + 'Si us plau, esperi que acabi la descàrrega actual'; @override String get desktop => 'Escriptori'; @@ -500,7 +506,8 @@ class AppLocalizationsCa extends AppLocalizations { String get please_sponsor => 'Si us plau, patrocina/dona'; @override - String get spotube_description => 'Spotube, un client lleuger, multiplataforma i gratuït de Spotify'; + String get spotube_description => + 'Spotube, un client lleuger, multiplataforma i gratuït de Spotify'; @override String get version => 'Versió'; @@ -532,7 +539,8 @@ class AppLocalizationsCa extends AppLocalizations { String get license => 'Llicència'; @override - String get credentials_will_not_be_shared_disclaimer => 'No es preocupi, les seves credencials no seran recollides ni compartides amb ningú'; + String get credentials_will_not_be_shared_disclaimer => + 'No es preocupi, les seves credencials no seran recollides ni compartides amb ningú'; @override String get know_how_to_login => 'No sap com fer-ho?'; @@ -576,19 +584,23 @@ class AppLocalizationsCa extends AppLocalizations { String get piped_instance => 'Instància del servidor Piped'; @override - String get piped_description => 'La instància del servidor Piped a utilitzar per la coincidència de cançons'; + String get piped_description => + 'La instància del servidor Piped a utilitzar per la coincidència de cançons'; @override - String get piped_warning => 'Algunes poden no funcionar bé, utilitzi-les sota el seu propi risc'; + String get piped_warning => + 'Algunes poden no funcionar bé, utilitzi-les sota el seu propi risc'; @override String get invidious_instance => 'Instància del servidor Invidious'; @override - String get invidious_description => 'La instància del servidor Invidious per fer coincidir pistes'; + String get invidious_description => + 'La instància del servidor Invidious per fer coincidir pistes'; @override - String get invidious_warning => 'Algunes instàncies podrien no funcionar bé. Feu-les servir sota la vostra responsabilitat'; + String get invidious_warning => + 'Algunes instàncies podrien no funcionar bé. Feu-les servir sota la vostra responsabilitat'; @override String get generate => 'Generar'; @@ -599,10 +611,12 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Substituir totes les cançons descarregades'; + String get replace_downloaded_tracks => + 'Substituir totes les cançons descarregades'; @override - String get skip_download_tracks => 'Ometre la descàrrega de totes les cançons descarregades'; + String get skip_download_tracks => + 'Ometre la descàrrega de totes les cançons descarregades'; @override String get do_you_want_to_replace => 'Vol substituir la cançó existent?'; @@ -703,7 +717,8 @@ class AppLocalizationsCa extends AppLocalizations { String get are_you_sure => 'Està segur?'; @override - String get generating_playlist => 'Generant la seva llista de reproducció personalitzada...'; + String get generating_playlist => + 'Generant la seva llista de reproducció personalitzada...'; @override String selected_count_tracks(Object count) { @@ -711,22 +726,28 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get download_warning => 'Si descarrega totes les cançons de cop, està piratejant música clarament i causant dany a la societat creativa de la música. Espero que sigui conscient d\'això i sempre intenti respectar i recolzar la forta feina dels artístes'; + String get download_warning => + 'Si descarrega totes les cançons de cop, està piratejant música clarament i causant dany a la societat creativa de la música. Espero que sigui conscient d\'això i sempre intenti respectar i recolzar la forta feina dels artístes'; @override - String get download_ip_ban_warning => 'Per cert, la seva IP pot ser bloquejada a YouTube degut a solicituds de descàrrega excessives. El bloqueig d\'IP vol dir que no podrà utilitzar YouTube (fins i tot si ha iniciat sessió) durant un mínim de 2-3 meses desde esa dirección IP. I Spotube no es fa responsable si això succeeix en alguna ocasió'; + String get download_ip_ban_warning => + 'Per cert, la seva IP pot ser bloquejada a YouTube degut a solicituds de descàrrega excessives. El bloqueig d\'IP vol dir que no podrà utilitzar YouTube (fins i tot si ha iniciat sessió) durant un mínim de 2-3 meses desde esa dirección IP. I Spotube no es fa responsable si això succeeix en alguna ocasió'; @override - String get by_clicking_accept_terms => 'Al fer clic a \'Acceptar\', acepta els següents termes:'; + String get by_clicking_accept_terms => + 'Al fer clic a \'Acceptar\', acepta els següents termes:'; @override - String get download_agreement_1 => 'Se que estic piratejant música. Sóc dolent'; + String get download_agreement_1 => + 'Se que estic piratejant música. Sóc dolent'; @override - String get download_agreement_2 => 'Recolzaré l\'artista quan pugui i només ho faig perquè no tinc diners per comprar el seu art'; + String get download_agreement_2 => + 'Recolzaré l\'artista quan pugui i només ho faig perquè no tinc diners per comprar el seu art'; @override - String get download_agreement_3 => 'Sóc completament conscient que la meva IP pot ser bloqueada per YouTube i no responsabilizo a Spotube ni als seus propietaris/contribuents per qualsevol incident causat per la meva acció actual'; + String get download_agreement_3 => + 'Sóc completament conscient que la meva IP pot ser bloqueada per YouTube i no responsabilizo a Spotube ni als seus propietaris/contribuents per qualsevol incident causat per la meva acció actual'; @override String get decline => 'Rebutjar'; @@ -807,7 +828,8 @@ class AppLocalizationsCa extends AppLocalizations { String get failed_to_encrypt => 'Error al xifrar'; @override - String get encryption_failed_warning => 'Spotube utilitza el xifrado per emmagatzemar les seves dades de forma segura. Però ha fallat. Per tant, tornarà a un emmagatzament no segur\nSi estè utilizant Linux, asseguri\'s de tenir instal·lats els serveis secrets com gnome-keyring, kde-wallet i keepassxc'; + String get encryption_failed_warning => + 'Spotube utilitza el xifrado per emmagatzemar les seves dades de forma segura. Però ha fallat. Per tant, tornarà a un emmagatzament no segur\nSi estè utilizant Linux, asseguri\'s de tenir instal·lats els serveis secrets com gnome-keyring, kde-wallet i keepassxc'; @override String get querying_info => 'Consultant informació...'; @@ -881,7 +903,8 @@ class AppLocalizationsCa extends AppLocalizations { String get login => 'Inicia la sessió'; @override - String get login_with_your_lastfm => 'Inicia la sessió amb el teu compte de Last.fm'; + String get login_with_your_lastfm => + 'Inicia la sessió amb el teu compte de Last.fm'; @override String get scrobble_to_lastfm => 'Scrobble a Last.fm'; @@ -905,7 +928,8 @@ class AppLocalizationsCa extends AppLocalizations { String get friends => 'Amics'; @override - String get no_lyrics_available => 'Ho sentim, no es poden trobar les lletres d\'aquesta pista'; + String get no_lyrics_available => + 'Ho sentim, no es poden trobar les lletres d\'aquesta pista'; @override String get start_a_radio => 'Inicia una ràdio'; @@ -914,7 +938,8 @@ class AppLocalizationsCa extends AppLocalizations { String get how_to_start_radio => 'Com vols començar la ràdio?'; @override - String get replace_queue_question => 'Voleu substituir la cua actual o afegir-hi?'; + String get replace_queue_question => + 'Voleu substituir la cua actual o afegir-hi?'; @override String get endless_playback => 'Reproducció infinita'; @@ -923,7 +948,8 @@ class AppLocalizationsCa extends AppLocalizations { String get delete_playlist => 'Suprimeix la llista de reproducció'; @override - String get delete_playlist_confirmation => 'Esteu segur que voleu suprimir aquesta llista de reproducció?'; + String get delete_playlist_confirmation => + 'Esteu segur que voleu suprimir aquesta llista de reproducció?'; @override String get local_tracks => 'Pistes locals'; @@ -941,7 +967,8 @@ class AppLocalizationsCa extends AppLocalizations { String get freedom_of_music => '“Llibertat de la música”'; @override - String get freedom_of_music_palm => '“Llibertat de la música a la palma de la mà”'; + String get freedom_of_music_palm => + '“Llibertat de la música a la palma de la mà”'; @override String get get_started => 'Comencem'; @@ -950,13 +977,16 @@ class AppLocalizationsCa extends AppLocalizations { String get youtube_source_description => 'Recomanat i funciona millor.'; @override - String get piped_source_description => 'Et sents lliure? El mateix que YouTube però més lliure.'; + String get piped_source_description => + 'Et sents lliure? El mateix que YouTube però més lliure.'; @override - String get jiosaavn_source_description => 'El millor per a la regió del sud d\'Àsia.'; + String get jiosaavn_source_description => + 'El millor per a la regió del sud d\'Àsia.'; @override - String get invidious_source_description => 'Similar a Piped però amb més disponibilitat'; + String get invidious_source_description => + 'Similar a Piped però amb més disponibilitat'; @override String highest_quality(Object quality) { @@ -967,13 +997,15 @@ class AppLocalizationsCa extends AppLocalizations { String get select_audio_source => 'Seleccioneu la font d\'àudio'; @override - String get endless_playback_description => 'Afegiu automàticament noves cançons\nal final de la cua'; + String get endless_playback_description => + 'Afegiu automàticament noves cançons\nal final de la cua'; @override String get choose_your_region => 'Trieu la vostra regió'; @override - String get choose_your_region_description => 'Això ajudarà a Spotube a mostrar-vos el contingut adequat\nper a la vostra ubicació.'; + String get choose_your_region_description => + 'Això ajudarà a Spotube a mostrar-vos el contingut adequat\nper a la vostra ubicació.'; @override String get choose_your_language => 'Trieu el vostre idioma'; @@ -982,7 +1014,8 @@ class AppLocalizationsCa extends AppLocalizations { String get help_project_grow => 'Ajuda a fer créixer aquest projecte'; @override - String get help_project_grow_description => 'Spotube és un projecte de codi obert. Podeu ajudar a fer créixer aquest projecte contribuint al projecte, informant d\'errors o suggerint noves funcionalitats.'; + String get help_project_grow_description => + 'Spotube és un projecte de codi obert. Podeu ajudar a fer créixer aquest projecte contribuint al projecte, informant d\'errors o suggerint noves funcionalitats.'; @override String get contribute_on_github => 'Contribueix a GitHub'; @@ -997,7 +1030,8 @@ class AppLocalizationsCa extends AppLocalizations { String get enable_connect => 'Habilita la connexió'; @override - String get enable_connect_description => 'Controla Spotube des d\'altres dispositius'; + String get enable_connect_description => + 'Controla Spotube des d\'altres dispositius'; @override String get devices => 'Dispositius'; @@ -1076,7 +1110,8 @@ class AppLocalizationsCa extends AppLocalizations { String get choose_the_device => 'Tria el dispositiu:'; @override - String get multiple_device_connected => 'Hi ha diversos dispositius connectats.\nTria el dispositiu on vols realitzar aquesta acció'; + String get multiple_device_connected => + 'Hi ha diversos dispositius connectats.\nTria el dispositiu on vols realitzar aquesta acció'; @override String get nothing_found => 'No s\'ha trobat res'; @@ -1149,7 +1184,8 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Comissions de streaming (hipotètic)'; + String get streaming_fees_hypothetical => + 'Comissions de streaming (hipotètic)'; @override String get minutes_listened => 'minuts escoltats'; @@ -1171,7 +1207,8 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1257,8 @@ class AppLocalizationsCa extends AppLocalizations { String get webview_not_found => 'No s\'ha trobat el Webview'; @override - String get webview_not_found_description => 'No hi ha cap temps d\'execució de Webview instal·lat al dispositiu.\nSi està instal·lat, assegureu-vos que estigui en el environment PATH\n\nDesprés d\'instal·lar-lo, reinicieu l\'aplicació'; + String get webview_not_found_description => + 'No hi ha cap temps d\'execució de Webview instal·lat al dispositiu.\nSi està instal·lat, assegureu-vos que estigui en el environment PATH\n\nDesprés d\'instal·lar-lo, reinicieu l\'aplicació'; @override String get unsupported_platform => 'Plataforma no compatible'; @@ -1293,7 +1331,8 @@ class AppLocalizationsCa extends AppLocalizations { String get not_following_artists => 'No estàs seguint cap artista'; @override - String get no_favorite_albums_yet => 'Sembla que encara no has afegit cap àlbum als teus favorits'; + String get no_favorite_albums_yet => + 'Sembla que encara no has afegit cap àlbum als teus favorits'; @override String get no_logs_found => 'No s\'han trobat registres'; @@ -1317,7 +1356,8 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'En macOS/Linux/Unix com a sistemes operatius, establir el camí a .zshrc/.bashrc/.bash_profile etc. no funcionarà.\nHas de configurar el camí al fitxer de configuració de la shell'; + String get youtube_engine_unix_issue_message => + 'En macOS/Linux/Unix com a sistemes operatius, establir el camí a .zshrc/.bashrc/.bash_profile etc. no funcionarà.\nHas de configurar el camí al fitxer de configuració de la shell'; @override String get download => 'Descarregar'; @@ -1335,7 +1375,8 @@ class AppLocalizationsCa extends AppLocalizations { String get edit_port => 'Editar port'; @override - String get port_helper_msg => 'El valor per defecte és -1, que indica un número aleatori. Si teniu un tallafoc configurat, es recomana establir-ho.'; + String get port_helper_msg => + 'El valor per defecte és -1, que indica un número aleatori. Si teniu un tallafoc configurat, es recomana establir-ho.'; @override String connect_request(Object client) { @@ -1343,5 +1384,6 @@ class AppLocalizationsCa extends AppLocalizations { } @override - String get connection_request_denied => 'Connexió denegada. L\'usuari ha denegat l\'accés.'; + String get connection_request_denied => + 'Connexió denegada. L\'usuari ha denegat l\'accés.'; } diff --git a/lib/l10n/generated/app_localizations_cs.dart b/lib/l10n/generated/app_localizations_cs.dart index a6e6e86d..0b084314 100644 --- a/lib/l10n/generated/app_localizations_cs.dart +++ b/lib/l10n/generated/app_localizations_cs.dart @@ -437,7 +437,8 @@ class AppLocalizationsCs extends AppLocalizations { String get sync_album_color => 'Synchronizovat barvu alba'; @override - String get sync_album_color_description => 'Používá dominantní barvu obalu alba jako barvu akcentu'; + String get sync_album_color_description => + 'Používá dominantní barvu obalu alba jako barvu akcentu'; @override String get playback => 'Přehrávání'; @@ -455,7 +456,8 @@ class AppLocalizationsCs extends AppLocalizations { String get pre_download_play => 'Předstáhnout a přehrát'; @override - String get pre_download_play_description => 'Místo streamování audia stáhnout skladbu a přehrát (doporučeno pro uživatele s rychlejším internetem)'; + String get pre_download_play_description => + 'Místo streamování audia stáhnout skladbu a přehrát (doporučeno pro uživatele s rychlejším internetem)'; @override String get skip_non_music => 'Přeskočit nehudební segmenty (SponsorBlock)'; @@ -500,7 +502,8 @@ class AppLocalizationsCs extends AppLocalizations { String get please_sponsor => 'Sponzorovat/darovat'; @override - String get spotube_description => 'Spotube, rychlý, multiplatformní, bezplatný Spotify klient'; + String get spotube_description => + 'Spotube, rychlý, multiplatformní, bezplatný Spotify klient'; @override String get version => 'Verze'; @@ -532,7 +535,8 @@ class AppLocalizationsCs extends AppLocalizations { String get license => 'Licence'; @override - String get credentials_will_not_be_shared_disclaimer => 'Nebojte, žádné z vašich údajů nebudou shromažďovány ani s nikým sdíleny'; + String get credentials_will_not_be_shared_disclaimer => + 'Nebojte, žádné z vašich údajů nebudou shromažďovány ani s nikým sdíleny'; @override String get know_how_to_login => 'Nevíte, jak na to?'; @@ -576,19 +580,23 @@ class AppLocalizationsCs extends AppLocalizations { String get piped_instance => 'Instance serveru Piped'; @override - String get piped_description => 'Instance serveru Piped, kterou použít pro hledání skladeb'; + String get piped_description => + 'Instance serveru Piped, kterou použít pro hledání skladeb'; @override - String get piped_warning => 'Některé z nich nemusí dobře fungovat. Používejte na vlastní riziko'; + String get piped_warning => + 'Některé z nich nemusí dobře fungovat. Používejte na vlastní riziko'; @override String get invidious_instance => 'Instance serveru Invidious'; @override - String get invidious_description => 'Instance serveru Invidious pro párování stop'; + String get invidious_description => + 'Instance serveru Invidious pro párování stop'; @override - String get invidious_warning => 'Některé instance nemusí fungovat správně. Používejte na vlastní riziko'; + String get invidious_warning => + 'Některé instance nemusí fungovat správně. Používejte na vlastní riziko'; @override String get generate => 'Generovat'; @@ -602,7 +610,8 @@ class AppLocalizationsCs extends AppLocalizations { String get replace_downloaded_tracks => 'Nahradit všechny stažené skladby'; @override - String get skip_download_tracks => 'Přeskočit stahování všech stažených skladeb'; + String get skip_download_tracks => + 'Přeskočit stahování všech stažených skladeb'; @override String get do_you_want_to_replace => 'Chcete nahradit existující skladbu??'; @@ -711,22 +720,27 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get download_warning => 'Pokud stáhnete všechny skladby najednou, pirátíte tím hudbu a škodíte kreativní společnosti hudby. Doufám, že jste si toho vědomi. Vždy se snažte respektovat a podporovat tvrdou práci umělců'; + String get download_warning => + 'Pokud stáhnete všechny skladby najednou, pirátíte tím hudbu a škodíte kreativní společnosti hudby. Doufám, že jste si toho vědomi. Vždy se snažte respektovat a podporovat tvrdou práci umělců'; @override - String get download_ip_ban_warning => 'Mimochodem, vaše IP může být na YouTube zablokována kvůli nadměrným požadavkům na stahování. Blokování IP znamená, že nemůžete používat YouTube (i když jste přihlášeni) alespoň 2-3 měsíce ze zařízení s touto IP. A Spotube nenese žádnou odpovědnost, pokud se to někdy stane'; + String get download_ip_ban_warning => + 'Mimochodem, vaše IP může být na YouTube zablokována kvůli nadměrným požadavkům na stahování. Blokování IP znamená, že nemůžete používat YouTube (i když jste přihlášeni) alespoň 2-3 měsíce ze zařízení s touto IP. A Spotube nenese žádnou odpovědnost, pokud se to někdy stane'; @override - String get by_clicking_accept_terms => 'Kliknutím na \'přijmout\' souhlasíte s následujícími podmínkami:'; + String get by_clicking_accept_terms => + 'Kliknutím na \'přijmout\' souhlasíte s následujícími podmínkami:'; @override String get download_agreement_1 => 'Vím, že pirátím hudbu. Jsem špatný'; @override - String get download_agreement_2 => 'Budu podporovat umělce, kdekoliv to bude možné, a dělám to jen proto, že nemám peníze na koupi jejich umění'; + String get download_agreement_2 => + 'Budu podporovat umělce, kdekoliv to bude možné, a dělám to jen proto, že nemám peníze na koupi jejich umění'; @override - String get download_agreement_3 => 'Jsem si naprosto vědom toho, že moje IP může být na YouTube zablokována a nenesu žádnou odpovědnost za nehody způsobené mým současným jednáním'; + String get download_agreement_3 => + 'Jsem si naprosto vědom toho, že moje IP může být na YouTube zablokována a nenesu žádnou odpovědnost za nehody způsobené mým současným jednáním'; @override String get decline => 'Odmítnout'; @@ -807,7 +821,8 @@ class AppLocalizationsCs extends AppLocalizations { String get failed_to_encrypt => 'Šifrování selhalo'; @override - String get encryption_failed_warning => 'Spotube používá šifrování k bezpečnému ukládání vašich dat. Ale selhalo. Takže se vrátí k nezabezpečenému úložišti\nPokud používáte linux, ujistěte se, že máte nainstalovanou jakoukoli službu k ukládání bezpečnostních pověření (gnome-keyring, kde-wallet, keepassxc atd.)'; + String get encryption_failed_warning => + 'Spotube používá šifrování k bezpečnému ukládání vašich dat. Ale selhalo. Takže se vrátí k nezabezpečenému úložišti\nPokud používáte linux, ujistěte se, že máte nainstalovanou jakoukoli službu k ukládání bezpečnostních pověření (gnome-keyring, kde-wallet, keepassxc atd.)'; @override String get querying_info => 'Získávání informací...'; @@ -881,7 +896,8 @@ class AppLocalizationsCs extends AppLocalizations { String get login => 'Přihlásit se'; @override - String get login_with_your_lastfm => 'Přihlásit se pomocí vašeho Last.fm účtu'; + String get login_with_your_lastfm => + 'Přihlásit se pomocí vašeho Last.fm účtu'; @override String get scrobble_to_lastfm => 'Scrobble na Last.fm'; @@ -905,7 +921,8 @@ class AppLocalizationsCs extends AppLocalizations { String get friends => 'Přátelé'; @override - String get no_lyrics_available => 'Omlouváme se, není možné najít texty pro tuto skladbu'; + String get no_lyrics_available => + 'Omlouváme se, není možné najít texty pro tuto skladbu'; @override String get start_a_radio => 'Vytvořit rádio'; @@ -914,7 +931,8 @@ class AppLocalizationsCs extends AppLocalizations { String get how_to_start_radio => 'Jak chcete vytvořit rádio?'; @override - String get replace_queue_question => 'Chcete nahradit aktuální frontu nebo k ní přidat?'; + String get replace_queue_question => + 'Chcete nahradit aktuální frontu nebo k ní přidat?'; @override String get endless_playback => 'Nekonečné přehrávání'; @@ -923,7 +941,8 @@ class AppLocalizationsCs extends AppLocalizations { String get delete_playlist => 'Smazat playlist'; @override - String get delete_playlist_confirmation => 'Jste si jisti, že chcete smazat tento playlist?'; + String get delete_playlist_confirmation => + 'Jste si jisti, že chcete smazat tento playlist?'; @override String get local_tracks => 'Místní skladby'; @@ -950,13 +969,15 @@ class AppLocalizationsCs extends AppLocalizations { String get youtube_source_description => 'Doporučeno a funguje nejlépe.'; @override - String get piped_source_description => 'Nechcete být sledováni? Stejné jako YouTube, ale respektuje soukromí.'; + String get piped_source_description => + 'Nechcete být sledováni? Stejné jako YouTube, ale respektuje soukromí.'; @override String get jiosaavn_source_description => 'Nejlepší pro jihoasijský region.'; @override - String get invidious_source_description => 'Podobné Piped, ale s vyšší dostupností'; + String get invidious_source_description => + 'Podobné Piped, ale s vyšší dostupností'; @override String highest_quality(Object quality) { @@ -967,13 +988,15 @@ class AppLocalizationsCs extends AppLocalizations { String get select_audio_source => 'Vyberte zdroj zvuku'; @override - String get endless_playback_description => 'Automaticky přidávat nové skladby\nna konec fronty'; + String get endless_playback_description => + 'Automaticky přidávat nové skladby\nna konec fronty'; @override String get choose_your_region => 'Vyberte svůj region'; @override - String get choose_your_region_description => 'To pomůže Spotube ukázat vám správný obsah\npro vaši lokalitu.'; + String get choose_your_region_description => + 'To pomůže Spotube ukázat vám správný obsah\npro vaši lokalitu.'; @override String get choose_your_language => 'Vyberte svůj jazyk'; @@ -982,7 +1005,8 @@ class AppLocalizationsCs extends AppLocalizations { String get help_project_grow => 'Pomozte tomuto projektu růst'; @override - String get help_project_grow_description => 'Spotube je open-source projekt. Můžete pomoci tomuto projektu růst tím, že přispějete do projektu, nahlásíte chyby nebo navrhnete nové funkce.'; + String get help_project_grow_description => + 'Spotube je open-source projekt. Můžete pomoci tomuto projektu růst tím, že přispějete do projektu, nahlásíte chyby nebo navrhnete nové funkce.'; @override String get contribute_on_github => 'Přispějte na GitHub'; @@ -997,7 +1021,8 @@ class AppLocalizationsCs extends AppLocalizations { String get enable_connect => 'Povolit ovládání'; @override - String get enable_connect_description => 'Ovládejte Spotube z jiného zařízení'; + String get enable_connect_description => + 'Ovládejte Spotube z jiného zařízení'; @override String get devices => 'Zařízení'; @@ -1076,7 +1101,8 @@ class AppLocalizationsCs extends AppLocalizations { String get choose_the_device => 'Vyberte zařízení:'; @override - String get multiple_device_connected => 'Je připojeno více zařízení.\nVyberte zařízení, na kterém chcete provést tuto akci'; + String get multiple_device_connected => + 'Je připojeno více zařízení.\nVyberte zařízení, na kterém chcete provést tuto akci'; @override String get nothing_found => 'Nic nenalezeno'; @@ -1149,7 +1175,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Poplatky za streamování (hypotetické)'; + String get streaming_fees_hypothetical => + 'Poplatky za streamování (hypotetické)'; @override String get minutes_listened => 'Poslouchané minuty'; @@ -1171,7 +1198,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1248,8 @@ class AppLocalizationsCs extends AppLocalizations { String get webview_not_found => 'Webview nebyl nalezen'; @override - String get webview_not_found_description => 'Na vašem zařízení není nainstalováno žádné runtime prostředí Webview.\nPokud je nainstalováno, ujistěte se, že je v environment PATH\n\nPo instalaci restartujte aplikaci'; + String get webview_not_found_description => + 'Na vašem zařízení není nainstalováno žádné runtime prostředí Webview.\nPokud je nainstalováno, ujistěte se, že je v environment PATH\n\nPo instalaci restartujte aplikaci'; @override String get unsupported_platform => 'Nepodporovaná platforma'; @@ -1281,19 +1310,22 @@ class AppLocalizationsCs extends AppLocalizations { String get view_all => 'Zobrazit vše'; @override - String get no_tracks_added_yet => 'Zdá se, že jste ještě nepřidali žádné skladby'; + String get no_tracks_added_yet => + 'Zdá se, že jste ještě nepřidali žádné skladby'; @override String get no_tracks => 'Zdá se, že zde nejsou žádné skladby'; @override - String get no_tracks_listened_yet => 'Zdá se, že jste ještě nic neposlouchali'; + String get no_tracks_listened_yet => + 'Zdá se, že jste ještě nic neposlouchali'; @override String get not_following_artists => 'Nezajímáte se o žádné umělce'; @override - String get no_favorite_albums_yet => 'Zdá se, že jste ještě nepřidali žádné alba mezi oblíbené'; + String get no_favorite_albums_yet => + 'Zdá se, že jste ještě nepřidali žádné alba mezi oblíbené'; @override String get no_logs_found => 'Žádné záznamy nenalezeny'; @@ -1317,7 +1349,8 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'V macOS/Linux/Unixových systémech nebude fungovat nastavení cesty v .zshrc/.bashrc/.bash_profile atd.\nMusíte nastavit cestu v konfiguračním souboru shellu'; + String get youtube_engine_unix_issue_message => + 'V macOS/Linux/Unixových systémech nebude fungovat nastavení cesty v .zshrc/.bashrc/.bash_profile atd.\nMusíte nastavit cestu v konfiguračním souboru shellu'; @override String get download => 'Stáhnout'; @@ -1335,7 +1368,8 @@ class AppLocalizationsCs extends AppLocalizations { String get edit_port => 'Upravit port'; @override - String get port_helper_msg => 'Výchozí hodnota je -1, což znamená náhodné číslo. Pokud máte nakonfigurován firewall, doporučuje se to nastavit.'; + String get port_helper_msg => + 'Výchozí hodnota je -1, což znamená náhodné číslo. Pokud máte nakonfigurován firewall, doporučuje se to nastavit.'; @override String connect_request(Object client) { @@ -1343,5 +1377,6 @@ class AppLocalizationsCs extends AppLocalizations { } @override - String get connection_request_denied => 'Připojení bylo zamítnuto. Uživatel odmítl přístup.'; + String get connection_request_denied => + 'Připojení bylo zamítnuto. Uživatel odmítl přístup.'; } diff --git a/lib/l10n/generated/app_localizations_de.dart b/lib/l10n/generated/app_localizations_de.dart index 7b1da343..1187eadd 100644 --- a/lib/l10n/generated/app_localizations_de.dart +++ b/lib/l10n/generated/app_localizations_de.dart @@ -161,7 +161,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get add_artist_to_blacklist => 'Künstler zur Schwarzen Liste hinzufügen'; + String get add_artist_to_blacklist => + 'Künstler zur Schwarzen Liste hinzufügen'; @override String get top_tracks => 'Top-Titel'; @@ -407,7 +408,8 @@ class AppLocalizationsDe extends AppLocalizations { String get layout_mode => 'Layout-Modus'; @override - String get override_layout_settings => 'Responsiven Layout-Modus-Einstellungen überschreiben'; + String get override_layout_settings => + 'Responsiven Layout-Modus-Einstellungen überschreiben'; @override String get adaptive => 'Adaptiv'; @@ -437,7 +439,8 @@ class AppLocalizationsDe extends AppLocalizations { String get sync_album_color => 'Albumfarbe synchronisieren'; @override - String get sync_album_color_description => 'Verwendet die dominante Farbe des Album Covers als Akzentfarbe'; + String get sync_album_color_description => + 'Verwendet die dominante Farbe des Album Covers als Akzentfarbe'; @override String get playback => 'Wiedergabe'; @@ -455,16 +458,19 @@ class AppLocalizationsDe extends AppLocalizations { String get pre_download_play => 'Vorab herunterladen und abspielen'; @override - String get pre_download_play_description => 'Anstatt Audio zu streamen, Bytes herunterladen und abspielen (Empfohlen für Benutzer mit hoher Bandbreite)'; + String get pre_download_play_description => + 'Anstatt Audio zu streamen, Bytes herunterladen und abspielen (Empfohlen für Benutzer mit hoher Bandbreite)'; @override - String get skip_non_music => 'Überspringe Nicht-Musik-Segmente (SponsorBlock)'; + String get skip_non_music => + 'Überspringe Nicht-Musik-Segmente (SponsorBlock)'; @override String get blacklist_description => 'Gesperrte Titel und Künstler'; @override - String get wait_for_download_to_finish => 'Bitte warten Sie, bis der aktuelle Download abgeschlossen ist'; + String get wait_for_download_to_finish => + 'Bitte warten Sie, bis der aktuelle Download abgeschlossen ist'; @override String get desktop => 'Desktop'; @@ -500,7 +506,8 @@ class AppLocalizationsDe extends AppLocalizations { String get please_sponsor => 'Bitte unterstützen/Spenden Sie'; @override - String get spotube_description => 'Spotube, ein leichtgewichtiger, plattformübergreifender und kostenloser Spotify-Client'; + String get spotube_description => + 'Spotube, ein leichtgewichtiger, plattformübergreifender und kostenloser Spotify-Client'; @override String get version => 'Version'; @@ -532,13 +539,15 @@ class AppLocalizationsDe extends AppLocalizations { String get license => 'Lizenz'; @override - String get credentials_will_not_be_shared_disclaimer => 'Keine Sorge, Ihre Anmeldeinformationen werden nicht erfasst oder mit anderen geteilt'; + String get credentials_will_not_be_shared_disclaimer => + 'Keine Sorge, Ihre Anmeldeinformationen werden nicht erfasst oder mit anderen geteilt'; @override String get know_how_to_login => 'Wissen Sie nicht, wie es geht?'; @override - String get follow_step_by_step_guide => 'Befolgen Sie die schrittweise Anleitung'; + String get follow_step_by_step_guide => + 'Befolgen Sie die schrittweise Anleitung'; @override String cookie_name_cookie(Object name) { @@ -576,19 +585,23 @@ class AppLocalizationsDe extends AppLocalizations { String get piped_instance => 'Piped-Serverinstanz'; @override - String get piped_description => 'Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll'; + String get piped_description => + 'Die Piped-Serverinstanz, die zur Titelzuordnung verwendet werden soll'; @override - String get piped_warning => 'Einige von ihnen funktionieren möglicherweise nicht gut. Verwende sie also auf eigenes Risiko'; + String get piped_warning => + 'Einige von ihnen funktionieren möglicherweise nicht gut. Verwende sie also auf eigenes Risiko'; @override String get invidious_instance => 'Invidious-Serverinstanz'; @override - String get invidious_description => 'Die Invidious-Serverinstanz zur Titelerkennung'; + String get invidious_description => + 'Die Invidious-Serverinstanz zur Titelerkennung'; @override - String get invidious_warning => 'Einige Instanzen funktionieren möglicherweise nicht gut. Benutzung auf eigene Gefahr'; + String get invidious_warning => + 'Einige Instanzen funktionieren möglicherweise nicht gut. Benutzung auf eigene Gefahr'; @override String get generate => 'Generieren'; @@ -599,13 +612,16 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Alle heruntergeladenen Titel ersetzen'; + String get replace_downloaded_tracks => + 'Alle heruntergeladenen Titel ersetzen'; @override - String get skip_download_tracks => 'Das Herunterladen aller heruntergeladenen Titel überspringen'; + String get skip_download_tracks => + 'Das Herunterladen aller heruntergeladenen Titel überspringen'; @override - String get do_you_want_to_replace => 'Möchtest du den vorhandenen Track ersetzen?'; + String get do_you_want_to_replace => + 'Möchtest du den vorhandenen Track ersetzen?'; @override String get replace => 'Ersetzen'; @@ -703,7 +719,8 @@ class AppLocalizationsDe extends AppLocalizations { String get are_you_sure => 'Bist du sicher?'; @override - String get generating_playlist => 'Erstelle deine individuelle Wiedergabeliste...'; + String get generating_playlist => + 'Erstelle deine individuelle Wiedergabeliste...'; @override String selected_count_tracks(Object count) { @@ -711,22 +728,28 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get download_warning => 'Wenn du alle Titel in großen Mengen herunterlädst, betreibst du eindeutig Raubkopien von Musik und schadest der kreativen Gesellschaft der Musik. Ich hoffe, dir ist dies bewusst. Versuche immer, die harte Arbeit der Künstler zu respektieren und zu unterstützen.'; + String get download_warning => + 'Wenn du alle Titel in großen Mengen herunterlädst, betreibst du eindeutig Raubkopien von Musik und schadest der kreativen Gesellschaft der Musik. Ich hoffe, dir ist dies bewusst. Versuche immer, die harte Arbeit der Künstler zu respektieren und zu unterstützen.'; @override - String get download_ip_ban_warning => 'Übrigens, deine IP-Adresse kann aufgrund übermäßiger Downloadanfragen von YouTube gesperrt werden. Eine IP-Sperre bedeutet, dass du YouTube (auch wenn du angemeldet bist) für mindestens 2-3 Monate von diesem IP-Gerät aus nicht nutzen kannst. Spotube übernimmt keine Verantwortung, falls dies jemals geschieht.'; + String get download_ip_ban_warning => + 'Übrigens, deine IP-Adresse kann aufgrund übermäßiger Downloadanfragen von YouTube gesperrt werden. Eine IP-Sperre bedeutet, dass du YouTube (auch wenn du angemeldet bist) für mindestens 2-3 Monate von diesem IP-Gerät aus nicht nutzen kannst. Spotube übernimmt keine Verantwortung, falls dies jemals geschieht.'; @override - String get by_clicking_accept_terms => 'Durch Klicken auf \'Akzeptieren\' stimmst du den folgenden Bedingungen zu:'; + String get by_clicking_accept_terms => + 'Durch Klicken auf \'Akzeptieren\' stimmst du den folgenden Bedingungen zu:'; @override - String get download_agreement_1 => 'Ich weiß, dass ich Raubkopien von Musik betreibe. Ich bin böse.'; + String get download_agreement_1 => + 'Ich weiß, dass ich Raubkopien von Musik betreibe. Ich bin böse.'; @override - String get download_agreement_2 => 'Ich werde die Künstler, wo immer ich kann, unterstützen, und ich tue dies nur, weil ich kein Geld habe, um ihre Kunst zu kaufen.'; + String get download_agreement_2 => + 'Ich werde die Künstler, wo immer ich kann, unterstützen, und ich tue dies nur, weil ich kein Geld habe, um ihre Kunst zu kaufen.'; @override - String get download_agreement_3 => 'Mir ist vollkommen bewusst, dass meine IP-Adresse auf YouTube gesperrt werden kann, und ich halte Spotube oder seine Eigentümer/Mitarbeiter nicht für etwaige Unfälle verantwortlich, die durch meine derzeitige Handlung verursacht werden.'; + String get download_agreement_3 => + 'Mir ist vollkommen bewusst, dass meine IP-Adresse auf YouTube gesperrt werden kann, und ich halte Spotube oder seine Eigentümer/Mitarbeiter nicht für etwaige Unfälle verantwortlich, die durch meine derzeitige Handlung verursacht werden.'; @override String get decline => 'Ablehnen'; @@ -807,7 +830,8 @@ class AppLocalizationsDe extends AppLocalizations { String get failed_to_encrypt => 'Verschlüsselung fehlgeschlagen'; @override - String get encryption_failed_warning => 'Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben'; + String get encryption_failed_warning => + 'Spotube verwendet Verschlüsselung, um Ihre Daten sicher zu speichern. Dies ist jedoch fehlgeschlagen. Daher wird es auf unsichere Speicherung zurückgreifen\nWenn Sie Linux verwenden, stellen Sie bitte sicher, dass Sie Secret-Services wie gnome-keyring, kde-wallet und keepassxc installiert haben'; @override String get querying_info => 'Abfrageinformationen...'; @@ -824,7 +848,8 @@ class AppLocalizationsDe extends AppLocalizations { String get you_are_offline => 'Sie sind derzeit offline'; @override - String get connection_restored => 'Ihre Internetverbindung wurde wiederhergestellt'; + String get connection_restored => + 'Ihre Internetverbindung wurde wiederhergestellt'; @override String get use_system_title_bar => 'System-Titelleiste verwenden'; @@ -905,7 +930,8 @@ class AppLocalizationsDe extends AppLocalizations { String get friends => 'Freunde'; @override - String get no_lyrics_available => 'Entschuldigung, Texte für diesen Track konnten nicht gefunden werden'; + String get no_lyrics_available => + 'Entschuldigung, Texte für diesen Track konnten nicht gefunden werden'; @override String get start_a_radio => 'Radio starten'; @@ -914,7 +940,8 @@ class AppLocalizationsDe extends AppLocalizations { String get how_to_start_radio => 'Wie möchten Sie das Radio starten?'; @override - String get replace_queue_question => 'Möchten Sie die aktuelle Wiedergabeliste ersetzen oder hinzufügen?'; + String get replace_queue_question => + 'Möchten Sie die aktuelle Wiedergabeliste ersetzen oder hinzufügen?'; @override String get endless_playback => 'Endlose Wiedergabe'; @@ -923,7 +950,8 @@ class AppLocalizationsDe extends AppLocalizations { String get delete_playlist => 'Wiedergabeliste löschen'; @override - String get delete_playlist_confirmation => 'Sind Sie sicher, dass Sie diese Wiedergabeliste löschen möchten?'; + String get delete_playlist_confirmation => + 'Sind Sie sicher, dass Sie diese Wiedergabeliste löschen möchten?'; @override String get local_tracks => 'Lokale Titel'; @@ -941,22 +969,27 @@ class AppLocalizationsDe extends AppLocalizations { String get freedom_of_music => '“Freiheit der Musik”'; @override - String get freedom_of_music_palm => '“Freiheit der Musik in Ihrer Handfläche”'; + String get freedom_of_music_palm => + '“Freiheit der Musik in Ihrer Handfläche”'; @override String get get_started => 'Lass uns anfangen'; @override - String get youtube_source_description => 'Empfohlen und funktioniert am besten.'; + String get youtube_source_description => + 'Empfohlen und funktioniert am besten.'; @override - String get piped_source_description => 'Fühlen Sie sich frei? Wie YouTube, aber viel freier.'; + String get piped_source_description => + 'Fühlen Sie sich frei? Wie YouTube, aber viel freier.'; @override - String get jiosaavn_source_description => 'Am besten für die südasiatische Region.'; + String get jiosaavn_source_description => + 'Am besten für die südasiatische Region.'; @override - String get invidious_source_description => 'Ähnlich wie Piped, aber mit höherer Verfügbarkeit'; + String get invidious_source_description => + 'Ähnlich wie Piped, aber mit höherer Verfügbarkeit'; @override String highest_quality(Object quality) { @@ -967,13 +1000,15 @@ class AppLocalizationsDe extends AppLocalizations { String get select_audio_source => 'Audioquelle auswählen'; @override - String get endless_playback_description => 'Neue Lieder automatisch\nam Ende der Wiedergabeliste hinzufügen'; + String get endless_playback_description => + 'Neue Lieder automatisch\nam Ende der Wiedergabeliste hinzufügen'; @override String get choose_your_region => 'Wählen Sie Ihre Region'; @override - String get choose_your_region_description => 'Dies wird Spotube helfen, Ihnen den richtigen Inhalt\nfür Ihren Standort anzuzeigen.'; + String get choose_your_region_description => + 'Dies wird Spotube helfen, Ihnen den richtigen Inhalt\nfür Ihren Standort anzuzeigen.'; @override String get choose_your_language => 'Wählen Sie Ihre Sprache'; @@ -982,7 +1017,8 @@ class AppLocalizationsDe extends AppLocalizations { String get help_project_grow => 'Helfen Sie diesem Projekt zu wachsen'; @override - String get help_project_grow_description => 'Spotube ist ein Open-Source-Projekt. Sie können diesem Projekt helfen, indem Sie zum Projekt beitragen, Fehler melden oder neue Funktionen vorschlagen.'; + String get help_project_grow_description => + 'Spotube ist ein Open-Source-Projekt. Sie können diesem Projekt helfen, indem Sie zum Projekt beitragen, Fehler melden oder neue Funktionen vorschlagen.'; @override String get contribute_on_github => 'Auf GitHub beitragen'; @@ -997,7 +1033,8 @@ class AppLocalizationsDe extends AppLocalizations { String get enable_connect => 'Verbindung aktivieren'; @override - String get enable_connect_description => 'Spotube von anderen Geräten steuern'; + String get enable_connect_description => + 'Spotube von anderen Geräten steuern'; @override String get devices => 'Geräte'; @@ -1076,7 +1113,8 @@ class AppLocalizationsDe extends AppLocalizations { String get choose_the_device => 'Wähle das Gerät:'; @override - String get multiple_device_connected => 'Es sind mehrere Geräte verbunden.\nWähle das Gerät, auf dem diese Aktion ausgeführt werden soll'; + String get multiple_device_connected => + 'Es sind mehrere Geräte verbunden.\nWähle das Gerät, auf dem diese Aktion ausgeführt werden soll'; @override String get nothing_found => 'Nichts gefunden'; @@ -1171,7 +1209,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1191,7 +1230,8 @@ class AppLocalizationsDe extends AppLocalizations { String get summary_streamed_overall => 'Insgesamt gestreamt'; @override - String get summary_owed_to_artists => 'Den Künstlern geschuldet\nDiesen Monat'; + String get summary_owed_to_artists => + 'Den Künstlern geschuldet\nDiesen Monat'; @override String get summary_artists => 'Künstler'; @@ -1220,7 +1260,8 @@ class AppLocalizationsDe extends AppLocalizations { String get webview_not_found => 'Webview nicht gefunden'; @override - String get webview_not_found_description => 'Es ist keine Webview-Laufzeitumgebung auf Ihrem Gerät installiert.\nFalls installiert, stellen Sie sicher, dass es im environment PATH ist\n\nNach der Installation starten Sie die App neu'; + String get webview_not_found_description => + 'Es ist keine Webview-Laufzeitumgebung auf Ihrem Gerät installiert.\nFalls installiert, stellen Sie sicher, dass es im environment PATH ist\n\nNach der Installation starten Sie die App neu'; @override String get unsupported_platform => 'Nicht unterstützte Plattform'; @@ -1252,7 +1293,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get export_cache_confirmation => 'Möchten Sie diese Dateien exportieren nach'; + String get export_cache_confirmation => + 'Möchten Sie diese Dateien exportieren nach'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1287,13 +1329,15 @@ class AppLocalizationsDe extends AppLocalizations { String get no_tracks => 'Es sieht so aus, als ob hier keine Titel sind.'; @override - String get no_tracks_listened_yet => 'Es scheint, dass Sie noch nichts gehört haben.'; + String get no_tracks_listened_yet => + 'Es scheint, dass Sie noch nichts gehört haben.'; @override String get not_following_artists => 'Sie folgen noch keinem Künstler.'; @override - String get no_favorite_albums_yet => 'Es sieht so aus, als ob Sie noch keine Alben zu Ihren Favoriten hinzugefügt haben.'; + String get no_favorite_albums_yet => + 'Es sieht so aus, als ob Sie noch keine Alben zu Ihren Favoriten hinzugefügt haben.'; @override String get no_logs_found => 'Keine Protokolle gefunden'; @@ -1317,7 +1361,8 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'In macOS/Linux/unixähnlichen Betriebssystemen funktioniert das Setzen des Pfads in .zshrc/.bashrc/.bash_profile usw. nicht.\nSie müssen den Pfad in der Shell-Konfigurationsdatei festlegen.'; + String get youtube_engine_unix_issue_message => + 'In macOS/Linux/unixähnlichen Betriebssystemen funktioniert das Setzen des Pfads in .zshrc/.bashrc/.bash_profile usw. nicht.\nSie müssen den Pfad in der Shell-Konfigurationsdatei festlegen.'; @override String get download => 'Herunterladen'; @@ -1335,7 +1380,8 @@ class AppLocalizationsDe extends AppLocalizations { String get edit_port => 'Port bearbeiten'; @override - String get port_helper_msg => 'Der Standardwert ist -1, was eine zufällige Zahl bedeutet. Wenn Sie eine Firewall konfiguriert haben, wird empfohlen, dies einzustellen.'; + String get port_helper_msg => + 'Der Standardwert ist -1, was eine zufällige Zahl bedeutet. Wenn Sie eine Firewall konfiguriert haben, wird empfohlen, dies einzustellen.'; @override String connect_request(Object client) { @@ -1343,5 +1389,6 @@ class AppLocalizationsDe extends AppLocalizations { } @override - String get connection_request_denied => 'Verbindung abgelehnt. Benutzer hat den Zugriff verweigert.'; + String get connection_request_denied => + 'Verbindung abgelehnt. Benutzer hat den Zugriff verweigert.'; } diff --git a/lib/l10n/generated/app_localizations_en.dart b/lib/l10n/generated/app_localizations_en.dart index d1f9ba0c..cbecb68c 100644 --- a/lib/l10n/generated/app_localizations_en.dart +++ b/lib/l10n/generated/app_localizations_en.dart @@ -407,7 +407,8 @@ class AppLocalizationsEn extends AppLocalizations { String get layout_mode => 'Layout Mode'; @override - String get override_layout_settings => 'Override responsive layout mode settings'; + String get override_layout_settings => + 'Override responsive layout mode settings'; @override String get adaptive => 'Adaptive'; @@ -437,7 +438,8 @@ class AppLocalizationsEn extends AppLocalizations { String get sync_album_color => 'Sync album color'; @override - String get sync_album_color_description => 'Uses the dominant color of the album art as the accent color'; + String get sync_album_color_description => + 'Uses the dominant color of the album art as the accent color'; @override String get playback => 'Playback'; @@ -455,7 +457,8 @@ class AppLocalizationsEn extends AppLocalizations { String get pre_download_play => 'Pre-download and play'; @override - String get pre_download_play_description => 'Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)'; + String get pre_download_play_description => + 'Instead of streaming audio, download bytes and play instead (Recommended for higher bandwidth users)'; @override String get skip_non_music => 'Skip non-music segments (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsEn extends AppLocalizations { String get blacklist_description => 'Blacklisted tracks and artists'; @override - String get wait_for_download_to_finish => 'Please wait for the current download to finish'; + String get wait_for_download_to_finish => + 'Please wait for the current download to finish'; @override String get desktop => 'Desktop'; @@ -500,7 +504,8 @@ class AppLocalizationsEn extends AppLocalizations { String get please_sponsor => 'Please Sponsor/Donate'; @override - String get spotube_description => 'Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept'; + String get spotube_description => + 'Open source extensible music streaming platform and app, based on BYOMM (Bring your own music metadata) concept'; @override String get version => 'Version'; @@ -532,7 +537,8 @@ class AppLocalizationsEn extends AppLocalizations { String get license => 'License'; @override - String get credentials_will_not_be_shared_disclaimer => 'Don\'t worry, any of your credentials won\'t be collected or shared with anyone'; + String get credentials_will_not_be_shared_disclaimer => + 'Don\'t worry, any of your credentials won\'t be collected or shared with anyone'; @override String get know_how_to_login => 'Don\'t know how to do this?'; @@ -576,19 +582,23 @@ class AppLocalizationsEn extends AppLocalizations { String get piped_instance => 'Piped Server Instance'; @override - String get piped_description => 'The Piped server instance to use for track matching'; + String get piped_description => + 'The Piped server instance to use for track matching'; @override - String get piped_warning => 'Some of them might not work well. So use at your own risk'; + String get piped_warning => + 'Some of them might not work well. So use at your own risk'; @override String get invidious_instance => 'Invidious Server Instance'; @override - String get invidious_description => 'The Invidious server instance to use for track matching'; + String get invidious_description => + 'The Invidious server instance to use for track matching'; @override - String get invidious_warning => 'Some of them might not work well. So use at your own risk'; + String get invidious_warning => + 'Some of them might not work well. So use at your own risk'; @override String get generate => 'Generate'; @@ -605,7 +615,8 @@ class AppLocalizationsEn extends AppLocalizations { String get skip_download_tracks => 'Skip downloading all downloaded tracks'; @override - String get do_you_want_to_replace => 'Do you want to replace the existing track??'; + String get do_you_want_to_replace => + 'Do you want to replace the existing track??'; @override String get replace => 'Replace'; @@ -711,22 +722,27 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get download_warning => 'If you download all Tracks at bulk you\'re clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist\'s hard work'; + String get download_warning => + 'If you download all Tracks at bulk you\'re clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist\'s hard work'; @override - String get download_ip_ban_warning => 'BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can\'t use YouTube (even if you\'re logged in) for at least 2-3 months from that IP device. And Spotube doesn\'t hold any responsibility if this ever happens'; + String get download_ip_ban_warning => + 'BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can\'t use YouTube (even if you\'re logged in) for at least 2-3 months from that IP device. And Spotube doesn\'t hold any responsibility if this ever happens'; @override - String get by_clicking_accept_terms => 'By clicking \'accept\' you agree to following terms:'; + String get by_clicking_accept_terms => + 'By clicking \'accept\' you agree to following terms:'; @override String get download_agreement_1 => 'I know I\'m pirating Music. I\'m bad'; @override - String get download_agreement_2 => 'I\'ll support the Artist wherever I can and I\'m only doing this because I don\'t have money to buy their art'; + String get download_agreement_2 => + 'I\'ll support the Artist wherever I can and I\'m only doing this because I don\'t have money to buy their art'; @override - String get download_agreement_3 => 'I\'m completely aware that my IP can get blocked on YouTube & I don\'t hold Spotube or his owners/contributors responsible for any accidents caused by my current action'; + String get download_agreement_3 => + 'I\'m completely aware that my IP can get blocked on YouTube & I don\'t hold Spotube or his owners/contributors responsible for any accidents caused by my current action'; @override String get decline => 'Decline'; @@ -807,7 +823,8 @@ class AppLocalizationsEn extends AppLocalizations { String get failed_to_encrypt => 'Failed to encrypt'; @override - String get encryption_failed_warning => 'Spotube uses encryption to securely store your data. But failed to do so. So it\'ll fallback to insecure storage\nIf you\'re using linux, please make sure you\'ve any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed'; + String get encryption_failed_warning => + 'Spotube uses encryption to securely store your data. But failed to do so. So it\'ll fallback to insecure storage\nIf you\'re using linux, please make sure you\'ve any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed'; @override String get querying_info => 'Querying info...'; @@ -914,7 +931,8 @@ class AppLocalizationsEn extends AppLocalizations { String get how_to_start_radio => 'How do you want to start the radio?'; @override - String get replace_queue_question => 'Do you want to replace the current queue or append to it?'; + String get replace_queue_question => + 'Do you want to replace the current queue or append to it?'; @override String get endless_playback => 'Endless Playback'; @@ -923,7 +941,8 @@ class AppLocalizationsEn extends AppLocalizations { String get delete_playlist => 'Delete Playlist'; @override - String get delete_playlist_confirmation => 'Are you sure you want to delete this playlist?'; + String get delete_playlist_confirmation => + 'Are you sure you want to delete this playlist?'; @override String get local_tracks => 'Local Tracks'; @@ -941,7 +960,8 @@ class AppLocalizationsEn extends AppLocalizations { String get freedom_of_music => '“Freedom of Music”'; @override - String get freedom_of_music_palm => '“Freedom of Music in the palm of your hand”'; + String get freedom_of_music_palm => + '“Freedom of Music in the palm of your hand”'; @override String get get_started => 'Let\'s get started'; @@ -950,13 +970,15 @@ class AppLocalizationsEn extends AppLocalizations { String get youtube_source_description => 'Recommended and works best.'; @override - String get piped_source_description => 'Feeling free? Same as YouTube but a lot free.'; + String get piped_source_description => + 'Feeling free? Same as YouTube but a lot free.'; @override String get jiosaavn_source_description => 'Best for South Asian region.'; @override - String get invidious_source_description => 'Similar to Piped but with higher availability.'; + String get invidious_source_description => + 'Similar to Piped but with higher availability.'; @override String highest_quality(Object quality) { @@ -967,13 +989,15 @@ class AppLocalizationsEn extends AppLocalizations { String get select_audio_source => 'Select Audio Source'; @override - String get endless_playback_description => 'Automatically append new songs\nto the end of the queue'; + String get endless_playback_description => + 'Automatically append new songs\nto the end of the queue'; @override String get choose_your_region => 'Choose your region'; @override - String get choose_your_region_description => 'This will help Spotube show you the right content\nfor your location.'; + String get choose_your_region_description => + 'This will help Spotube show you the right content\nfor your location.'; @override String get choose_your_language => 'Choose your language'; @@ -982,7 +1006,8 @@ class AppLocalizationsEn extends AppLocalizations { String get help_project_grow => 'Help this project grow'; @override - String get help_project_grow_description => 'Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.'; + String get help_project_grow_description => + 'Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.'; @override String get contribute_on_github => 'Contribute on GitHub'; @@ -1076,7 +1101,8 @@ class AppLocalizationsEn extends AppLocalizations { String get choose_the_device => 'Choose the device:'; @override - String get multiple_device_connected => 'There are multiple device connected.\nChoose the device you want this action to take place'; + String get multiple_device_connected => + 'There are multiple device connected.\nChoose the device you want this action to take place'; @override String get nothing_found => 'Nothing found'; @@ -1171,7 +1197,8 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1247,8 @@ class AppLocalizationsEn extends AppLocalizations { String get webview_not_found => 'Webview not found'; @override - String get webview_not_found_description => 'No webview runtime is installed in your device.\nIf it\'s installed make sure it\'s in the Environment PATH\n\nAfter installing, restart the app'; + String get webview_not_found_description => + 'No webview runtime is installed in your device.\nIf it\'s installed make sure it\'s in the Environment PATH\n\nAfter installing, restart the app'; @override String get unsupported_platform => 'Unsupported platform'; @@ -1252,7 +1280,8 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get export_cache_confirmation => 'Do you want to export these files to'; + String get export_cache_confirmation => + 'Do you want to export these files to'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1310,22 @@ class AppLocalizationsEn extends AppLocalizations { String get view_all => 'View all'; @override - String get no_tracks_added_yet => 'Looks like you haven\'t added any tracks yet'; + String get no_tracks_added_yet => + 'Looks like you haven\'t added any tracks yet'; @override String get no_tracks => 'Looks like there are no tracks here'; @override - String get no_tracks_listened_yet => 'Looks like you haven\'t listened to anything yet'; + String get no_tracks_listened_yet => + 'Looks like you haven\'t listened to anything yet'; @override String get not_following_artists => 'You\'re not following any artists'; @override - String get no_favorite_albums_yet => 'Looks like you haven\'t added any albums to your favorites yet'; + String get no_favorite_albums_yet => + 'Looks like you haven\'t added any albums to your favorites yet'; @override String get no_logs_found => 'No logs found'; @@ -1317,7 +1349,8 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'In macOS/Linux/unix like OS\'s, setting path on .zshrc/.bashrc/.bash_profile etc. won\'t work.\nYou need to set the path in the shell configuration file'; + String get youtube_engine_unix_issue_message => + 'In macOS/Linux/unix like OS\'s, setting path on .zshrc/.bashrc/.bash_profile etc. won\'t work.\nYou need to set the path in the shell configuration file'; @override String get download => 'Download'; @@ -1335,7 +1368,8 @@ class AppLocalizationsEn extends AppLocalizations { String get edit_port => 'Edit port'; @override - String get port_helper_msg => 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; + String get port_helper_msg => + 'Default is -1 which indicates random number. If you\'ve firewall configured, setting this is recommended.'; @override String connect_request(Object client) { @@ -1343,5 +1377,6 @@ class AppLocalizationsEn extends AppLocalizations { } @override - String get connection_request_denied => 'Connection denied. User denied access.'; + String get connection_request_denied => + 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_es.dart b/lib/l10n/generated/app_localizations_es.dart index 91026447..9fc35f31 100644 --- a/lib/l10n/generated/app_localizations_es.dart +++ b/lib/l10n/generated/app_localizations_es.dart @@ -356,7 +356,8 @@ class AppLocalizationsEs extends AppLocalizations { String get clear_all => 'Limpiar todo'; @override - String get show_hide_ui_on_hover => 'Mostrar/Ocultar interfaz al pasar el cursor'; + String get show_hide_ui_on_hover => + 'Mostrar/Ocultar interfaz al pasar el cursor'; @override String get always_on_top => 'Siempre visible'; @@ -407,7 +408,8 @@ class AppLocalizationsEs extends AppLocalizations { String get layout_mode => 'Modo de diseño'; @override - String get override_layout_settings => 'Anular la configuración del modo de diseño responsive'; + String get override_layout_settings => + 'Anular la configuración del modo de diseño responsive'; @override String get adaptive => 'Adaptable'; @@ -437,7 +439,8 @@ class AppLocalizationsEs extends AppLocalizations { String get sync_album_color => 'Sincronizar color del álbum'; @override - String get sync_album_color_description => 'Usa el color dominante del arte del álbum como color de acento'; + String get sync_album_color_description => + 'Usa el color dominante del arte del álbum como color de acento'; @override String get playback => 'Reproducción'; @@ -455,16 +458,19 @@ class AppLocalizationsEs extends AppLocalizations { String get pre_download_play => 'Pre-descargar y reproducir'; @override - String get pre_download_play_description => 'En lugar de transmitir audio, descarga bytes y reproduce en su lugar (recomendado para usuarios con mayor ancho de banda)'; + String get pre_download_play_description => + 'En lugar de transmitir audio, descarga bytes y reproduce en su lugar (recomendado para usuarios con mayor ancho de banda)'; @override - String get skip_non_music => 'Omitir segmentos que no son música (SponsorBlock)'; + String get skip_non_music => + 'Omitir segmentos que no son música (SponsorBlock)'; @override String get blacklist_description => 'Canciones y artistas en la lista negra'; @override - String get wait_for_download_to_finish => 'Por favor, espera a que termine la descarga actual'; + String get wait_for_download_to_finish => + 'Por favor, espera a que termine la descarga actual'; @override String get desktop => 'Escritorio'; @@ -500,7 +506,8 @@ class AppLocalizationsEs extends AppLocalizations { String get please_sponsor => 'Por favor, apoya/dona'; @override - String get spotube_description => 'Spotube, un cliente ligero, multiplataforma y gratuito de Spotify'; + String get spotube_description => + 'Spotube, un cliente ligero, multiplataforma y gratuito de Spotify'; @override String get version => 'Versión'; @@ -532,7 +539,8 @@ class AppLocalizationsEs extends AppLocalizations { String get license => 'Licencia'; @override - String get credentials_will_not_be_shared_disclaimer => 'No te preocupes, tus credenciales no serán recopiladas ni compartidas con nadie'; + String get credentials_will_not_be_shared_disclaimer => + 'No te preocupes, tus credenciales no serán recopiladas ni compartidas con nadie'; @override String get know_how_to_login => '¿No sabes cómo hacerlo?'; @@ -576,19 +584,23 @@ class AppLocalizationsEs extends AppLocalizations { String get piped_instance => 'Instancia del servidor Piped'; @override - String get piped_description => 'La instancia del servidor Piped a utilizar para la coincidencia de pistas'; + String get piped_description => + 'La instancia del servidor Piped a utilizar para la coincidencia de pistas'; @override - String get piped_warning => 'Algunas pueden no funcionar bien, úsalas bajo tu propio riesgo'; + String get piped_warning => + 'Algunas pueden no funcionar bien, úsalas bajo tu propio riesgo'; @override String get invidious_instance => 'Instancia del Servidor Invidious'; @override - String get invidious_description => 'La instancia del servidor Invidious para identificar pistas'; + String get invidious_description => + 'La instancia del servidor Invidious para identificar pistas'; @override - String get invidious_warning => 'Algunas instancias podrían no funcionar bien. Úselas bajo su propio riesgo'; + String get invidious_warning => + 'Algunas instancias podrían no funcionar bien. Úselas bajo su propio riesgo'; @override String get generate => 'Generar'; @@ -599,13 +611,16 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Reemplazar todas las canciones descargadas'; + String get replace_downloaded_tracks => + 'Reemplazar todas las canciones descargadas'; @override - String get skip_download_tracks => 'Omitir la descarga de todas las canciones descargadas'; + String get skip_download_tracks => + 'Omitir la descarga de todas las canciones descargadas'; @override - String get do_you_want_to_replace => '¿Deseas reemplazar la canción existente?'; + String get do_you_want_to_replace => + '¿Deseas reemplazar la canción existente?'; @override String get replace => 'Reemplazar'; @@ -703,7 +718,8 @@ class AppLocalizationsEs extends AppLocalizations { String get are_you_sure => '¿Estás seguro?'; @override - String get generating_playlist => 'Generando tu lista de reproducción personalizada...'; + String get generating_playlist => + 'Generando tu lista de reproducción personalizada...'; @override String selected_count_tracks(Object count) { @@ -711,22 +727,27 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get download_warning => 'Si descargas todas las canciones de golpe, estás claramente pirateando música y causando daño a la sociedad creativa de la música. Espero que seas consciente de esto y siempre intentes respetar y apoyar el arduo trabajo de los artistas'; + String get download_warning => + 'Si descargas todas las canciones de golpe, estás claramente pirateando música y causando daño a la sociedad creativa de la música. Espero que seas consciente de esto y siempre intentes respetar y apoyar el arduo trabajo de los artistas'; @override - String get download_ip_ban_warning => 'Por cierto, tu IP puede ser bloqueada en YouTube debido a solicitudes de descarga excesivas. El bloqueo de IP significa que no podrás usar YouTube (incluso si has iniciado sesión) durante al menos 2-3 meses desde esa dirección IP. Y Spotube no se hace responsable si esto ocurre alguna vez'; + String get download_ip_ban_warning => + 'Por cierto, tu IP puede ser bloqueada en YouTube debido a solicitudes de descarga excesivas. El bloqueo de IP significa que no podrás usar YouTube (incluso si has iniciado sesión) durante al menos 2-3 meses desde esa dirección IP. Y Spotube no se hace responsable si esto ocurre alguna vez'; @override - String get by_clicking_accept_terms => 'Al hacer clic en \'Aceptar\', aceptas los siguientes términos:'; + String get by_clicking_accept_terms => + 'Al hacer clic en \'Aceptar\', aceptas los siguientes términos:'; @override String get download_agreement_1 => 'Sé que estoy pirateando música. Soy malo'; @override - String get download_agreement_2 => 'Apoyaré al artista donde pueda y solo lo hago porque no tengo dinero para comprar su arte'; + String get download_agreement_2 => + 'Apoyaré al artista donde pueda y solo lo hago porque no tengo dinero para comprar su arte'; @override - String get download_agreement_3 => 'Soy completamente consciente de que mi IP puede ser bloqueada en YouTube y no responsabilizo a Spotube ni a sus dueños/contribuyentes por cualquier incidente causado por mi acción actual'; + String get download_agreement_3 => + 'Soy completamente consciente de que mi IP puede ser bloqueada en YouTube y no responsabilizo a Spotube ni a sus dueños/contribuyentes por cualquier incidente causado por mi acción actual'; @override String get decline => 'Rechazar'; @@ -807,7 +828,8 @@ class AppLocalizationsEs extends AppLocalizations { String get failed_to_encrypt => 'Error al cifrar'; @override - String get encryption_failed_warning => 'Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc'; + String get encryption_failed_warning => + 'Spotube utiliza el cifrado para almacenar sus datos de forma segura. Pero ha fallado. Por lo tanto, volverá a un almacenamiento no seguro\nSi está utilizando Linux, asegúrese de tener instalados servicios secretos como gnome-keyring, kde-wallet y keepassxc'; @override String get querying_info => 'Consultando información...'; @@ -881,7 +903,8 @@ class AppLocalizationsEs extends AppLocalizations { String get login => 'Iniciar sesión'; @override - String get login_with_your_lastfm => 'Iniciar sesión con tu cuenta de Last.fm'; + String get login_with_your_lastfm => + 'Iniciar sesión con tu cuenta de Last.fm'; @override String get scrobble_to_lastfm => 'Scrobble a Last.fm'; @@ -905,7 +928,8 @@ class AppLocalizationsEs extends AppLocalizations { String get friends => 'Amigos'; @override - String get no_lyrics_available => 'Lo siento, no se pueden encontrar las letras de esta pista'; + String get no_lyrics_available => + 'Lo siento, no se pueden encontrar las letras de esta pista'; @override String get start_a_radio => 'Iniciar una Radio'; @@ -914,7 +938,8 @@ class AppLocalizationsEs extends AppLocalizations { String get how_to_start_radio => '¿Cómo quieres iniciar la radio?'; @override - String get replace_queue_question => '¿Quieres reemplazar la lista de reproducción actual o añadir a ella?'; + String get replace_queue_question => + '¿Quieres reemplazar la lista de reproducción actual o añadir a ella?'; @override String get endless_playback => 'Reproducción Infinita'; @@ -923,7 +948,8 @@ class AppLocalizationsEs extends AppLocalizations { String get delete_playlist => 'Eliminar Lista de Reproducción'; @override - String get delete_playlist_confirmation => '¿Estás seguro de que quieres eliminar esta lista de reproducción?'; + String get delete_playlist_confirmation => + '¿Estás seguro de que quieres eliminar esta lista de reproducción?'; @override String get local_tracks => 'Pistas Locales'; @@ -941,7 +967,8 @@ class AppLocalizationsEs extends AppLocalizations { String get freedom_of_music => '“Libertad de la Música”'; @override - String get freedom_of_music_palm => '“Libertad de la Música en la palma de tu mano”'; + String get freedom_of_music_palm => + '“Libertad de la Música en la palma de tu mano”'; @override String get get_started => 'Empecemos'; @@ -950,13 +977,16 @@ class AppLocalizationsEs extends AppLocalizations { String get youtube_source_description => 'Recomendado y funciona mejor.'; @override - String get piped_source_description => '¿Te sientes libre? Igual que YouTube pero más libre.'; + String get piped_source_description => + '¿Te sientes libre? Igual que YouTube pero más libre.'; @override - String get jiosaavn_source_description => 'Lo mejor para la región del sur de Asia.'; + String get jiosaavn_source_description => + 'Lo mejor para la región del sur de Asia.'; @override - String get invidious_source_description => 'Similar a Piped, pero con mayor disponibilidad'; + String get invidious_source_description => + 'Similar a Piped, pero con mayor disponibilidad'; @override String highest_quality(Object quality) { @@ -967,13 +997,15 @@ class AppLocalizationsEs extends AppLocalizations { String get select_audio_source => 'Seleccionar Fuente de Audio'; @override - String get endless_playback_description => 'Añadir automáticamente nuevas canciones\nal final de la cola de reproducción'; + String get endless_playback_description => + 'Añadir automáticamente nuevas canciones\nal final de la cola de reproducción'; @override String get choose_your_region => 'Elige tu región'; @override - String get choose_your_region_description => 'Esto ayudará a Spotube a mostrarte el contenido adecuado\npara tu ubicación.'; + String get choose_your_region_description => + 'Esto ayudará a Spotube a mostrarte el contenido adecuado\npara tu ubicación.'; @override String get choose_your_language => 'Elige tu idioma'; @@ -982,7 +1014,8 @@ class AppLocalizationsEs extends AppLocalizations { String get help_project_grow => 'Ayuda a que este proyecto crezca'; @override - String get help_project_grow_description => 'Spotube es un proyecto de código abierto. Puedes ayudar a que este proyecto crezca contribuyendo al proyecto, informando errores o sugiriendo nuevas funciones.'; + String get help_project_grow_description => + 'Spotube es un proyecto de código abierto. Puedes ayudar a que este proyecto crezca contribuyendo al proyecto, informando errores o sugiriendo nuevas funciones.'; @override String get contribute_on_github => 'Contribuir en GitHub'; @@ -997,7 +1030,8 @@ class AppLocalizationsEs extends AppLocalizations { String get enable_connect => 'Habilitar conexión'; @override - String get enable_connect_description => 'Controla Spotube desde otros dispositivos'; + String get enable_connect_description => + 'Controla Spotube desde otros dispositivos'; @override String get devices => 'Dispositivos'; @@ -1076,7 +1110,8 @@ class AppLocalizationsEs extends AppLocalizations { String get choose_the_device => 'Elige el dispositivo:'; @override - String get multiple_device_connected => 'Hay múltiples dispositivos conectados.\nElige el dispositivo en el que deseas realizar esta acción'; + String get multiple_device_connected => + 'Hay múltiples dispositivos conectados.\nElige el dispositivo en el que deseas realizar esta acción'; @override String get nothing_found => 'Nada encontrado'; @@ -1149,7 +1184,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Tarifas de streaming (hipotéticas)'; + String get streaming_fees_hypothetical => + 'Tarifas de streaming (hipotéticas)'; @override String get minutes_listened => 'Minutos escuchados'; @@ -1171,7 +1207,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1257,8 @@ class AppLocalizationsEs extends AppLocalizations { String get webview_not_found => 'No se encontró el Webview'; @override - String get webview_not_found_description => 'No hay tiempo de ejecución de Webview instalado en su dispositivo.\nSi está instalado, asegúrese de que esté en el environment PATH\n\nDespués de instalar, reinicie la aplicación'; + String get webview_not_found_description => + 'No hay tiempo de ejecución de Webview instalado en su dispositivo.\nSi está instalado, asegúrese de que esté en el environment PATH\n\nDespués de instalar, reinicie la aplicación'; @override String get unsupported_platform => 'Plataforma no soportada'; @@ -1281,19 +1319,22 @@ class AppLocalizationsEs extends AppLocalizations { String get view_all => 'Ver todo'; @override - String get no_tracks_added_yet => 'Parece que aún no has agregado ninguna canción.'; + String get no_tracks_added_yet => + 'Parece que aún no has agregado ninguna canción.'; @override String get no_tracks => 'Parece que no hay canciones aquí.'; @override - String get no_tracks_listened_yet => 'Parece que no has escuchado nada todavía.'; + String get no_tracks_listened_yet => + 'Parece que no has escuchado nada todavía.'; @override String get not_following_artists => 'No sigues a ningún artista.'; @override - String get no_favorite_albums_yet => 'Parece que aún no has agregado ningún álbum a tus favoritos.'; + String get no_favorite_albums_yet => + 'Parece que aún no has agregado ningún álbum a tus favoritos.'; @override String get no_logs_found => 'No se encontraron registros'; @@ -1317,7 +1358,8 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'En macOS/Linux/sistemas operativos similares a Unix, establecer la ruta en .zshrc/.bashrc/.bash_profile etc. no funcionará.\nNecesitas establecer la ruta en el archivo de configuración del shell.'; + String get youtube_engine_unix_issue_message => + 'En macOS/Linux/sistemas operativos similares a Unix, establecer la ruta en .zshrc/.bashrc/.bash_profile etc. no funcionará.\nNecesitas establecer la ruta en el archivo de configuración del shell.'; @override String get download => 'Descargar'; @@ -1335,7 +1377,8 @@ class AppLocalizationsEs extends AppLocalizations { String get edit_port => 'Editar puerto'; @override - String get port_helper_msg => 'El valor predeterminado es -1, lo que indica un número aleatorio. Si tienes un firewall configurado, se recomienda establecer esto.'; + String get port_helper_msg => + 'El valor predeterminado es -1, lo que indica un número aleatorio. Si tienes un firewall configurado, se recomienda establecer esto.'; @override String connect_request(Object client) { @@ -1343,5 +1386,6 @@ class AppLocalizationsEs extends AppLocalizations { } @override - String get connection_request_denied => 'Conexión denegada. El usuario denegó el acceso.'; + String get connection_request_denied => + 'Conexión denegada. El usuario denegó el acceso.'; } diff --git a/lib/l10n/generated/app_localizations_eu.dart b/lib/l10n/generated/app_localizations_eu.dart index 18e92e03..0dba9560 100644 --- a/lib/l10n/generated/app_localizations_eu.dart +++ b/lib/l10n/generated/app_localizations_eu.dart @@ -356,7 +356,8 @@ class AppLocalizationsEu extends AppLocalizations { String get clear_all => 'Garbitu dena'; @override - String get show_hide_ui_on_hover => 'Erakutsi/Ezkutatu interfazea kurtsorea pasatzean'; + String get show_hide_ui_on_hover => + 'Erakutsi/Ezkutatu interfazea kurtsorea pasatzean'; @override String get always_on_top => 'Beti ikusgai'; @@ -407,7 +408,8 @@ class AppLocalizationsEu extends AppLocalizations { String get layout_mode => 'Diseinua'; @override - String get override_layout_settings => 'Responsive diseinuaren ezarpenak ezeztatu'; + String get override_layout_settings => + 'Responsive diseinuaren ezarpenak ezeztatu'; @override String get adaptive => 'Moldagarria'; @@ -437,7 +439,8 @@ class AppLocalizationsEu extends AppLocalizations { String get sync_album_color => 'Sinkronizatu albumaren kolorea'; @override - String get sync_album_color_description => 'Albumaren artearen kolore nagusia erabili azentu kolore bezala'; + String get sync_album_color_description => + 'Albumaren artearen kolore nagusia erabili azentu kolore bezala'; @override String get playback => 'Erreprodukzioa'; @@ -455,16 +458,19 @@ class AppLocalizationsEu extends AppLocalizations { String get pre_download_play => 'Aurre-deskargatu eta erreproduzitu'; @override - String get pre_download_play_description => 'Streaming egin beharrean, byte-ak deskargatu eta erreproduzitu (banda-zabalera handia duten erabiltzaileentzat gomendagarria)'; + String get pre_download_play_description => + 'Streaming egin beharrean, byte-ak deskargatu eta erreproduzitu (banda-zabalera handia duten erabiltzaileentzat gomendagarria)'; @override - String get skip_non_music => 'Musika ez diren segmentuak baztertu (SponsorBlock)'; + String get skip_non_music => + 'Musika ez diren segmentuak baztertu (SponsorBlock)'; @override String get blacklist_description => 'Zerrenda beltzeko abesti eta artistak'; @override - String get wait_for_download_to_finish => 'Mesedez, itxaron uneko deskarga bukatu arte'; + String get wait_for_download_to_finish => + 'Mesedez, itxaron uneko deskarga bukatu arte'; @override String get desktop => 'Mahaigaina'; @@ -500,7 +506,8 @@ class AppLocalizationsEu extends AppLocalizations { String get please_sponsor => 'Mesedez, babestu/diruz lagundu'; @override - String get spotube_description => 'Spotube, arina, plataforma-anitza eta doakoa den Spotify-ren bezeroa'; + String get spotube_description => + 'Spotube, arina, plataforma-anitza eta doakoa den Spotify-ren bezeroa'; @override String get version => 'Bertsioa'; @@ -532,7 +539,8 @@ class AppLocalizationsEu extends AppLocalizations { String get license => 'Lizentzia'; @override - String get credentials_will_not_be_shared_disclaimer => 'Ez arduratu, zure kredentzialak ez ditugu bilduko edo inorekin elkarbanatuko'; + String get credentials_will_not_be_shared_disclaimer => + 'Ez arduratu, zure kredentzialak ez ditugu bilduko edo inorekin elkarbanatuko'; @override String get know_how_to_login => 'Ez dakizu nola egin?'; @@ -576,19 +584,23 @@ class AppLocalizationsEu extends AppLocalizations { String get piped_instance => 'Piped zerbitzariaren instantzia'; @override - String get piped_description => 'Kanten koizidentzietan erabiltzeko Piped zerbitzariaren instantzia'; + String get piped_description => + 'Kanten koizidentzietan erabiltzeko Piped zerbitzariaren instantzia'; @override - String get piped_warning => 'Batzuk agian ez dute ongi funtzionatuko, zure ardurapean erabili'; + String get piped_warning => + 'Batzuk agian ez dute ongi funtzionatuko, zure ardurapean erabili'; @override String get invidious_instance => 'Invidious zerbitzari instantzia'; @override - String get invidious_description => 'Invidious zerbitzari instantzia, pistak bat egiteko'; + String get invidious_description => + 'Invidious zerbitzari instantzia, pistak bat egiteko'; @override - String get invidious_warning => 'Instantzia batzuek ez dute ondo funtzionatuko. Zure erantzukizunpean erabili'; + String get invidious_warning => + 'Instantzia batzuek ez dute ondo funtzionatuko. Zure erantzukizunpean erabili'; @override String get generate => 'Sortu'; @@ -599,10 +611,12 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Ordezkatu deskargatutako kanta guztiak'; + String get replace_downloaded_tracks => + 'Ordezkatu deskargatutako kanta guztiak'; @override - String get skip_download_tracks => 'Deskargatutako kanta guztien deskarga baztertu'; + String get skip_download_tracks => + 'Deskargatutako kanta guztien deskarga baztertu'; @override String get do_you_want_to_replace => 'Dagoen kanta ordezkatu nahi duzu??'; @@ -703,7 +717,8 @@ class AppLocalizationsEu extends AppLocalizations { String get are_you_sure => 'Ziur zaude?'; @override - String get generating_playlist => 'Zure pertsonalizatutako zerrenda sortzen...'; + String get generating_playlist => + 'Zure pertsonalizatutako zerrenda sortzen...'; @override String selected_count_tracks(Object count) { @@ -711,22 +726,28 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get download_warning => 'Abesti guztiak aldi berean deskargatuz gero, argi dago musika pirateatzen ari zarela eta musikaren gizarte sortzaileari kalte egiten diozula. Honen jakitun izan eta artisten lan gogorra errespetatu eta babestea espero dut'; + String get download_warning => + 'Abesti guztiak aldi berean deskargatuz gero, argi dago musika pirateatzen ari zarela eta musikaren gizarte sortzaileari kalte egiten diozula. Honen jakitun izan eta artisten lan gogorra errespetatu eta babestea espero dut'; @override - String get download_ip_ban_warning => 'Bidenabar, baliteke zure IPa YouTuben blokeatzea deskarga eskera gehiegi egiten badituzu. IPa blokeatzeak esan nahi du ezin izango duzula YouTube erabili (nahiz eta saioa hasia izan) gutxienez 2-3 hilabetez IP helbide horretatik. Eta Spotube ez da erantzule izango hori gertatzen bazaizu'; + String get download_ip_ban_warning => + 'Bidenabar, baliteke zure IPa YouTuben blokeatzea deskarga eskera gehiegi egiten badituzu. IPa blokeatzeak esan nahi du ezin izango duzula YouTube erabili (nahiz eta saioa hasia izan) gutxienez 2-3 hilabetez IP helbide horretatik. Eta Spotube ez da erantzule izango hori gertatzen bazaizu'; @override - String get by_clicking_accept_terms => '\'Onartu\' klikatzean, ondorengo baldintzak onartzen dituzu:'; + String get by_clicking_accept_terms => + '\'Onartu\' klikatzean, ondorengo baldintzak onartzen dituzu:'; @override - String get download_agreement_1 => 'Badakit musika pirateatzen ari naizela. Gaiztoa naiz'; + String get download_agreement_1 => + 'Badakit musika pirateatzen ari naizela. Gaiztoa naiz'; @override - String get download_agreement_2 => 'Ahal dudanean lagunduko diot artistari baina oraingoz ez dut bere artea erosteko dirurik'; + String get download_agreement_2 => + 'Ahal dudanean lagunduko diot artistari baina oraingoz ez dut bere artea erosteko dirurik'; @override - String get download_agreement_3 => 'Erabat jakitun naiz YouTubek nire IPa blokea dezakeela eta ez diot Spotube-ri edo bere jabe/laguntzaileei erantzukizunik eskatuko nire oraingo jokaerak ekar ditzakeen arazoengatik'; + String get download_agreement_3 => + 'Erabat jakitun naiz YouTubek nire IPa blokea dezakeela eta ez diot Spotube-ri edo bere jabe/laguntzaileei erantzukizunik eskatuko nire oraingo jokaerak ekar ditzakeen arazoengatik'; @override String get decline => 'Baztertu'; @@ -807,7 +828,8 @@ class AppLocalizationsEu extends AppLocalizations { String get failed_to_encrypt => 'Errorea zifratzean'; @override - String get encryption_failed_warning => 'Spotube-ek zifratzea darabil datuak modu seguruan biltegiratzeko. Baina huts egin du. Hori dela eta, biltegiratzea ez da segurua izango\nLinux erabiltzen ari bazara, ziurtatu edozein sekretu-zerbitzu (gnome-keyring, kde-wallet, keepassxc etab.) instalatuta duzula'; + String get encryption_failed_warning => + 'Spotube-ek zifratzea darabil datuak modu seguruan biltegiratzeko. Baina huts egin du. Hori dela eta, biltegiratzea ez da segurua izango\nLinux erabiltzen ari bazara, ziurtatu edozein sekretu-zerbitzu (gnome-keyring, kde-wallet, keepassxc etab.) instalatuta duzula'; @override String get querying_info => 'Informazioa egiaztatzen...'; @@ -905,7 +927,8 @@ class AppLocalizationsEu extends AppLocalizations { String get friends => 'Lagunak'; @override - String get no_lyrics_available => 'Sentitzen dugu, ezin dira kanta honen hitzak aurkitu'; + String get no_lyrics_available => + 'Sentitzen dugu, ezin dira kanta honen hitzak aurkitu'; @override String get start_a_radio => 'Hasi Irrati bat'; @@ -914,7 +937,8 @@ class AppLocalizationsEu extends AppLocalizations { String get how_to_start_radio => 'Nola hasi nahi duzu irratia?'; @override - String get replace_queue_question => 'Uneko zerrenda ordezkatu nahi duzu edo bertan gehitu?'; + String get replace_queue_question => + 'Uneko zerrenda ordezkatu nahi duzu edo bertan gehitu?'; @override String get endless_playback => 'Amaigabeko erreprodukzioa'; @@ -923,7 +947,8 @@ class AppLocalizationsEu extends AppLocalizations { String get delete_playlist => 'Ezabatu zerrenda'; @override - String get delete_playlist_confirmation => 'Ziur zaude zerrenda ezabatu nahi duzula?'; + String get delete_playlist_confirmation => + 'Ziur zaude zerrenda ezabatu nahi duzula?'; @override String get local_tracks => 'Kanta lokalak'; @@ -950,13 +975,16 @@ class AppLocalizationsEu extends AppLocalizations { String get youtube_source_description => 'Gomendatua eta hobekien dabilena.'; @override - String get piped_source_description => 'Aske zara? YouTube bezala, baino askeago.'; + String get piped_source_description => + 'Aske zara? YouTube bezala, baino askeago.'; @override - String get jiosaavn_source_description => 'Asia hegoaldeko herrialdeetarako hoberena.'; + String get jiosaavn_source_description => + 'Asia hegoaldeko herrialdeetarako hoberena.'; @override - String get invidious_source_description => 'Piped-en antzekoa, baina eskuragarritasun handiagoarekin'; + String get invidious_source_description => + 'Piped-en antzekoa, baina eskuragarritasun handiagoarekin'; @override String highest_quality(Object quality) { @@ -967,13 +995,15 @@ class AppLocalizationsEu extends AppLocalizations { String get select_audio_source => 'Aukeratu Audio Iturria'; @override - String get endless_playback_description => 'Gehitu automatikoki kanta berriak\n ilararen bukaeran'; + String get endless_playback_description => + 'Gehitu automatikoki kanta berriak\n ilararen bukaeran'; @override String get choose_your_region => 'Aukeratu zure herrialdea'; @override - String get choose_your_region_description => 'Honekin Spotube-k zure kokalerakuari dagokion edukia\neskeiniko dizu.'; + String get choose_your_region_description => + 'Honekin Spotube-k zure kokalerakuari dagokion edukia\neskeiniko dizu.'; @override String get choose_your_language => 'Aukeratu zure hizkuntza'; @@ -982,7 +1012,8 @@ class AppLocalizationsEu extends AppLocalizations { String get help_project_grow => 'Lagundu proiektu honi hazten'; @override - String get help_project_grow_description => 'Spotube kode irekiko proiektu bat da. Proiektu hau hazten lagundu dezakezu, erroreak jakinaraziz edo ezaugarri berriak proposatuz.'; + String get help_project_grow_description => + 'Spotube kode irekiko proiektu bat da. Proiektu hau hazten lagundu dezakezu, erroreak jakinaraziz edo ezaugarri berriak proposatuz.'; @override String get contribute_on_github => 'GitHub-en lagundu'; @@ -997,7 +1028,8 @@ class AppLocalizationsEu extends AppLocalizations { String get enable_connect => 'Gaitu konexioa'; @override - String get enable_connect_description => 'Kontrolatu Spotube beste gailu batzuetatik'; + String get enable_connect_description => + 'Kontrolatu Spotube beste gailu batzuetatik'; @override String get devices => 'Gailuak'; @@ -1076,7 +1108,8 @@ class AppLocalizationsEu extends AppLocalizations { String get choose_the_device => 'Aukeratu gailua:'; @override - String get multiple_device_connected => 'Hainbat gailu daude konektatuta.\nAukeratu zein gailutan aplikatu nahi duzun ekintza hau'; + String get multiple_device_connected => + 'Hainbat gailu daude konektatuta.\nAukeratu zein gailutan aplikatu nahi duzun ekintza hau'; @override String get nothing_found => 'Ezer ez da aurkitu'; @@ -1149,7 +1182,8 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Streaming ordainketa (hipotetikoa)'; + String get streaming_fees_hypothetical => + 'Streaming ordainketa (hipotetikoa)'; @override String get minutes_listened => 'Entzundako minutuak'; @@ -1171,7 +1205,8 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1255,8 @@ class AppLocalizationsEu extends AppLocalizations { String get webview_not_found => 'Ez da Webview aurkitu'; @override - String get webview_not_found_description => 'Ez dago Webview abiarazte denbora-instalaziorik zure gailuan.\nInstalatuta badago, ziurtatu environment PATH-an dagoela\n\nInstalatu ondoren, berrabiarazi aplikazioa'; + String get webview_not_found_description => + 'Ez dago Webview abiarazte denbora-instalaziorik zure gailuan.\nInstalatuta badago, ziurtatu environment PATH-an dagoela\n\nInstalatu ondoren, berrabiarazi aplikazioa'; @override String get unsupported_platform => 'Plataforma ez onartua'; @@ -1252,7 +1288,8 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get export_cache_confirmation => 'Fitxategi hauek esportatu nahi al dituzu'; + String get export_cache_confirmation => + 'Fitxategi hauek esportatu nahi al dituzu'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1318,22 @@ class AppLocalizationsEu extends AppLocalizations { String get view_all => 'Ikusi guztia'; @override - String get no_tracks_added_yet => 'Dirudienez, oraindik ez duzu abestirik gehitu.'; + String get no_tracks_added_yet => + 'Dirudienez, oraindik ez duzu abestirik gehitu.'; @override String get no_tracks => 'Ez dirudi hemen abestirik dagoenik.'; @override - String get no_tracks_listened_yet => 'Dirudienez, oraindik ez duzu ezer entzun.'; + String get no_tracks_listened_yet => + 'Dirudienez, oraindik ez duzu ezer entzun.'; @override String get not_following_artists => 'Ez zaude artisten atzetik.'; @override - String get no_favorite_albums_yet => 'Dirudienez, oraindik ez duzu albumik gehitu zure gogokoen artean.'; + String get no_favorite_albums_yet => + 'Dirudienez, oraindik ez duzu albumik gehitu zure gogokoen artean.'; @override String get no_logs_found => 'Ez dira log-ak aurkitu'; @@ -1317,7 +1357,8 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/Unix bezalako sistemetan, .zshrc/.bashrc/.bash_profile bezalako fitxategietan bidearen ezarpenak ez dira funtzionatuko.\nBidearen ezarpena shell konfigurazio fitxategian egin behar duzu.'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/Unix bezalako sistemetan, .zshrc/.bashrc/.bash_profile bezalako fitxategietan bidearen ezarpenak ez dira funtzionatuko.\nBidearen ezarpena shell konfigurazio fitxategian egin behar duzu.'; @override String get download => 'Deskargatu'; @@ -1335,7 +1376,8 @@ class AppLocalizationsEu extends AppLocalizations { String get edit_port => 'Editatu portua'; @override - String get port_helper_msg => 'Lehenetsitako balioa -1 da, zenbaki aleatorioa adierazten duena. Su firewall konfiguratu baduzu, gomendatzen da hau ezartzea.'; + String get port_helper_msg => + 'Lehenetsitako balioa -1 da, zenbaki aleatorioa adierazten duena. Su firewall konfiguratu baduzu, gomendatzen da hau ezartzea.'; @override String connect_request(Object client) { @@ -1343,5 +1385,6 @@ class AppLocalizationsEu extends AppLocalizations { } @override - String get connection_request_denied => 'Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du.'; + String get connection_request_denied => + 'Konektatzea ukatu da. Erabiltzaileak sarbidea ukatu du.'; } diff --git a/lib/l10n/generated/app_localizations_fa.dart b/lib/l10n/generated/app_localizations_fa.dart index 1c0f9dfb..86c0cbbb 100644 --- a/lib/l10n/generated/app_localizations_fa.dart +++ b/lib/l10n/generated/app_localizations_fa.dart @@ -407,7 +407,8 @@ class AppLocalizationsFa extends AppLocalizations { String get layout_mode => 'حالت چیدمان'; @override - String get override_layout_settings => 'تنطیمات حالت واکنشگرای چیدمان را لغو کن'; + String get override_layout_settings => + 'تنطیمات حالت واکنشگرای چیدمان را لغو کن'; @override String get adaptive => 'قابل تطبیق'; @@ -437,7 +438,8 @@ class AppLocalizationsFa extends AppLocalizations { String get sync_album_color => 'هنگام سازی رنگ البوم'; @override - String get sync_album_color_description => 'از رنگ البوم هنرمند به عنوان رنگ تاکیدی استفاده میکند'; + String get sync_album_color_description => + 'از رنگ البوم هنرمند به عنوان رنگ تاکیدی استفاده میکند'; @override String get playback => 'پخش'; @@ -455,7 +457,8 @@ class AppLocalizationsFa extends AppLocalizations { String get pre_download_play => 'دانلود و پخش کنید'; @override - String get pre_download_play_description => 'به جای پخش جریانی صدا، بایت ها را دانلود کنید و به جای آن پخش کنید (برای کاربران با پهنای باند بالاتر توصیه می شود)'; + String get pre_download_play_description => + 'به جای پخش جریانی صدا، بایت ها را دانلود کنید و به جای آن پخش کنید (برای کاربران با پهنای باند بالاتر توصیه می شود)'; @override String get skip_non_music => 'رد شدن از پخش های غیر موسیقی (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsFa extends AppLocalizations { String get blacklist_description => 'آهنگ ها و هنرمند های در لیست سیاه'; @override - String get wait_for_download_to_finish => 'لطفا صبر کنید تا دانلود آهنگ جاری تمام شود'; + String get wait_for_download_to_finish => + 'لطفا صبر کنید تا دانلود آهنگ جاری تمام شود'; @override String get desktop => 'میز کار'; @@ -500,7 +504,8 @@ class AppLocalizationsFa extends AppLocalizations { String get please_sponsor => 'لطفا کمک/حمایت کنید'; @override - String get spotube_description => 'یک برنامه سبک و مولتی پلتفرم و رایگان برای همه استSpotube'; + String get spotube_description => + 'یک برنامه سبک و مولتی پلتفرم و رایگان برای همه استSpotube'; @override String get version => 'نسخه'; @@ -532,7 +537,8 @@ class AppLocalizationsFa extends AppLocalizations { String get license => 'مجوز'; @override - String get credentials_will_not_be_shared_disclaimer => 'نگران نباشید هیچ کدوما از اعتبارات شما جمع اوری نمیشود یا با کسی اشتراک گزاشته نمیشود'; + String get credentials_will_not_be_shared_disclaimer => + 'نگران نباشید هیچ کدوما از اعتبارات شما جمع اوری نمیشود یا با کسی اشتراک گزاشته نمیشود'; @override String get know_how_to_login => 'نمیدانی چگونه این کار را انجام بدهی؟'; @@ -579,7 +585,8 @@ class AppLocalizationsFa extends AppLocalizations { String get piped_description => 'مشکل در ارتباط با سرور در دریافت آهنگ ها'; @override - String get piped_warning => 'برخی از آنها ممکن است خوب کارنکند.بنابراین با مسولیت خود استفاده کنید'; + String get piped_warning => + 'برخی از آنها ممکن است خوب کارنکند.بنابراین با مسولیت خود استفاده کنید'; @override String get invidious_instance => 'نمونه سرور Invidious'; @@ -588,7 +595,8 @@ class AppLocalizationsFa extends AppLocalizations { String get invidious_description => 'نمونه سرور Invidious برای تطبیق آهنگ'; @override - String get invidious_warning => 'برخی از نمونه‌ها ممکن است به خوبی کار نکنند. با احتیاط استفاده کنید'; + String get invidious_warning => + 'برخی از نمونه‌ها ممکن است به خوبی کار نکنند. با احتیاط استفاده کنید'; @override String get generate => 'ایجاد'; @@ -599,13 +607,15 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'همه ی آهنگ های دانلود شده را جایگزین کنید'; + String get replace_downloaded_tracks => + 'همه ی آهنگ های دانلود شده را جایگزین کنید'; @override String get skip_download_tracks => 'همه ی آهنگ های دانلود شده را رد کنید'; @override - String get do_you_want_to_replace => 'ایا میخواهید آهنگ های موجود جایگزین کنید؟'; + String get do_you_want_to_replace => + 'ایا میخواهید آهنگ های موجود جایگزین کنید؟'; @override String get replace => 'جایگزین کردن'; @@ -711,22 +721,27 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get download_warning => 'اگر همه ی آهنگ ها را به صورت انبو دانلود کنید به وضوح در حال دزدی موسقی هستید و در حال اسیب وارد کردن به جامه ی خلاق هنری می باشید .امیدوارم که از این موضوع اگاه باشید .همیشه سعی کنید به کار سخت هنرمند اخترام بگذارید.'; + String get download_warning => + 'اگر همه ی آهنگ ها را به صورت انبو دانلود کنید به وضوح در حال دزدی موسقی هستید و در حال اسیب وارد کردن به جامه ی خلاق هنری می باشید .امیدوارم که از این موضوع اگاه باشید .همیشه سعی کنید به کار سخت هنرمند اخترام بگذارید.'; @override - String get download_ip_ban_warning => 'راستی آی پی شما می تواند در یوتوب به دلیل درخواست های دانلود بیش از حد معمول مسدود شود. بلوک آی پی به این معنی است که شما نمی توانید از یوتوب (حتی اگر وارد سیستم شده باشید) حداقل 2-3 ماه از آن دستگاه آی پی استفاده کنید. و Spotube هیچ مسئولیتی در صورت وقوع این اتفاق ندارد'; + String get download_ip_ban_warning => + 'راستی آی پی شما می تواند در یوتوب به دلیل درخواست های دانلود بیش از حد معمول مسدود شود. بلوک آی پی به این معنی است که شما نمی توانید از یوتوب (حتی اگر وارد سیستم شده باشید) حداقل 2-3 ماه از آن دستگاه آی پی استفاده کنید. و Spotube هیچ مسئولیتی در صورت وقوع این اتفاق ندارد'; @override - String get by_clicking_accept_terms => 'با کلیک بر روی قبول با شرایط زیر موافقت می کنید:'; + String get by_clicking_accept_terms => + 'با کلیک بر روی قبول با شرایط زیر موافقت می کنید:'; @override String get download_agreement_1 => 'من میدانم در حال دزدی هستم .من بد هستم'; @override - String get download_agreement_2 => 'من هر کجا ک بتوانم از هنرمندان حمایت میکنم اما این کارا فقط به دلیل اینکه توانایی مالی ندارم انجام میدهم'; + String get download_agreement_2 => + 'من هر کجا ک بتوانم از هنرمندان حمایت میکنم اما این کارا فقط به دلیل اینکه توانایی مالی ندارم انجام میدهم'; @override - String get download_agreement_3 => 'من کاملا میدانم که از طرف یوتوب بلاک میشم و این برنامه و مالکان را مسول این حادثه نمیدانم.'; + String get download_agreement_3 => + 'من کاملا میدانم که از طرف یوتوب بلاک میشم و این برنامه و مالکان را مسول این حادثه نمیدانم.'; @override String get decline => 'قبول نکردن'; @@ -807,7 +822,8 @@ class AppLocalizationsFa extends AppLocalizations { String get failed_to_encrypt => 'رمز گذاری نشده'; @override - String get encryption_failed_warning => 'Spotube از رمزگذاری برای ذخیره ایمن داده های شما استفاده می کند. اما موفق به انجام این کار نشد. بنابراین به فضای ذخیره‌سازی ناامن تبدیل می‌شود\nاگر از لینوکس استفاده می‌کنید، لطفاً مطمئن شوید که سرویس مخفی (gnome-keyring، kde-wallet، keepassxc و غیره) را نصب کرده‌اید.'; + String get encryption_failed_warning => + 'Spotube از رمزگذاری برای ذخیره ایمن داده های شما استفاده می کند. اما موفق به انجام این کار نشد. بنابراین به فضای ذخیره‌سازی ناامن تبدیل می‌شود\nاگر از لینوکس استفاده می‌کنید، لطفاً مطمئن شوید که سرویس مخفی (gnome-keyring، kde-wallet، keepassxc و غیره) را نصب کرده‌اید.'; @override String get querying_info => 'جستجو درباره '; @@ -905,7 +921,8 @@ class AppLocalizationsFa extends AppLocalizations { String get friends => 'دوستان'; @override - String get no_lyrics_available => 'متاسفیم، قادر به یافتن متن این قطعه نیستیم'; + String get no_lyrics_available => + 'متاسفیم، قادر به یافتن متن این قطعه نیستیم'; @override String get start_a_radio => 'شروع یک رادیو'; @@ -914,7 +931,8 @@ class AppLocalizationsFa extends AppLocalizations { String get how_to_start_radio => 'چگونه می‌خواهید رادیو را شروع کنید؟'; @override - String get replace_queue_question => 'آیا می‌خواهید لیست پخش فعلی را جایگزین کنید یا به آن اضافه کنید؟'; + String get replace_queue_question => + 'آیا می‌خواهید لیست پخش فعلی را جایگزین کنید یا به آن اضافه کنید؟'; @override String get endless_playback => 'پخش بی‌پایان'; @@ -923,7 +941,8 @@ class AppLocalizationsFa extends AppLocalizations { String get delete_playlist => 'حذف لیست پخش'; @override - String get delete_playlist_confirmation => 'آیا مطمئن هستید که می‌خواهید این لیست پخش را حذف کنید؟'; + String get delete_playlist_confirmation => + 'آیا مطمئن هستید که می‌خواهید این لیست پخش را حذف کنید؟'; @override String get local_tracks => 'موسیقی‌های محلی'; @@ -950,13 +969,15 @@ class AppLocalizationsFa extends AppLocalizations { String get youtube_source_description => 'پیشنهاد شده و بهترین عمل می‌کند.'; @override - String get piped_source_description => 'احساس آزادی می‌کنید؟ مانند یوتیوب اما بیشتر آزاد.'; + String get piped_source_description => + 'احساس آزادی می‌کنید؟ مانند یوتیوب اما بیشتر آزاد.'; @override String get jiosaavn_source_description => 'بهترین برای منطقه جنوب آسیا.'; @override - String get invidious_source_description => 'شبیه Piped اما با در دسترس بودن بیشتر'; + String get invidious_source_description => + 'شبیه Piped اما با در دسترس بودن بیشتر'; @override String highest_quality(Object quality) { @@ -967,13 +988,15 @@ class AppLocalizationsFa extends AppLocalizations { String get select_audio_source => 'انتخاب منبع صوتی'; @override - String get endless_playback_description => 'خودکار اضافه کردن آهنگ‌های جدید\nبه انتهای صف'; + String get endless_playback_description => + 'خودکار اضافه کردن آهنگ‌های جدید\nبه انتهای صف'; @override String get choose_your_region => 'منطقه خود را انتخاب کنید'; @override - String get choose_your_region_description => 'این به Spotube کمک می‌کند تا محتوای مناسبی را برای موقعیت شما نشان دهد.'; + String get choose_your_region_description => + 'این به Spotube کمک می‌کند تا محتوای مناسبی را برای موقعیت شما نشان دهد.'; @override String get choose_your_language => 'زبان خود را انتخاب کنید'; @@ -982,7 +1005,8 @@ class AppLocalizationsFa extends AppLocalizations { String get help_project_grow => 'کمک به رشد این پروژه'; @override - String get help_project_grow_description => 'Spotube یک پروژه متن باز است. شما می‌توانید با به پروژه کمک کردن، گزارش دادن اشکالات یا پیشنهاد ویژگی‌های جدید، به این پروژه کمک کنید.'; + String get help_project_grow_description => + 'Spotube یک پروژه متن باز است. شما می‌توانید با به پروژه کمک کردن، گزارش دادن اشکالات یا پیشنهاد ویژگی‌های جدید، به این پروژه کمک کنید.'; @override String get contribute_on_github => 'مشارکت در GitHub'; @@ -1076,7 +1100,8 @@ class AppLocalizationsFa extends AppLocalizations { String get choose_the_device => 'دستگاه را انتخاب کنید:'; @override - String get multiple_device_connected => 'چندین دستگاه متصل هستند.\nدستگاهی را انتخاب کنید که می‌خواهید این عملیات بر روی آن انجام شود'; + String get multiple_device_connected => + 'چندین دستگاه متصل هستند.\nدستگاهی را انتخاب کنید که می‌خواهید این عملیات بر روی آن انجام شود'; @override String get nothing_found => 'چیزی پیدا نشد'; @@ -1171,7 +1196,8 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1246,8 @@ class AppLocalizationsFa extends AppLocalizations { String get webview_not_found => 'وب‌ویو پیدا نشد'; @override - String get webview_not_found_description => 'هیچ اجرای وب‌ویو روی دستگاه شما نصب نشده است.\nدر صورت نصب، مطمئن شوید که در environment PATH قرار دارد\n\nپس از نصب، برنامه را مجدداً راه‌اندازی کنید'; + String get webview_not_found_description => + 'هیچ اجرای وب‌ویو روی دستگاه شما نصب نشده است.\nدر صورت نصب، مطمئن شوید که در environment PATH قرار دارد\n\nپس از نصب، برنامه را مجدداً راه‌اندازی کنید'; @override String get unsupported_platform => 'پلتفرم پشتیبانی نمی‌شود'; @@ -1241,7 +1268,8 @@ class AppLocalizationsFa extends AppLocalizations { String get clear_cache => 'پاک کردن حافظه موقت'; @override - String get clear_cache_confirmation => 'آیا می‌خواهید حافظه موقت را پاک کنید؟'; + String get clear_cache_confirmation => + 'آیا می‌خواهید حافظه موقت را پاک کنید؟'; @override String get export_cache_files => 'صادر کردن فایل‌های حافظه موقت'; @@ -1252,7 +1280,8 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get export_cache_confirmation => 'آیا می‌خواهید این فایل‌ها را صادر کنید به'; + String get export_cache_confirmation => + 'آیا می‌خواهید این فایل‌ها را صادر کنید به'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,7 +1310,8 @@ class AppLocalizationsFa extends AppLocalizations { String get view_all => 'مشاهده همه'; @override - String get no_tracks_added_yet => 'به نظر می‌رسد هنوز هیچ آهنگی اضافه نکرده‌اید.'; + String get no_tracks_added_yet => + 'به نظر می‌رسد هنوز هیچ آهنگی اضافه نکرده‌اید.'; @override String get no_tracks => 'به نظر می‌رسد هیچ آهنگی در اینجا وجود ندارد.'; @@ -1293,7 +1323,8 @@ class AppLocalizationsFa extends AppLocalizations { String get not_following_artists => 'شما هیچ هنرمندی را دنبال نمی‌کنید.'; @override - String get no_favorite_albums_yet => 'به نظر می‌رسد هنوز هیچ آلبومی را به علاقه‌مندی‌هایتان اضافه نکرده‌اید.'; + String get no_favorite_albums_yet => + 'به نظر می‌رسد هنوز هیچ آلبومی را به علاقه‌مندی‌هایتان اضافه نکرده‌اید.'; @override String get no_logs_found => 'هیچ لاگی پیدا نشد'; @@ -1317,7 +1348,8 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'در macOS/Linux/سیستم‌عامل‌های مشابه Unix، تنظیم مسیر در .zshrc/.bashrc/.bash_profile و غیره کار نمی‌کند.\nباید مسیر را در فایل پیکربندی شل تنظیم کنید.'; + String get youtube_engine_unix_issue_message => + 'در macOS/Linux/سیستم‌عامل‌های مشابه Unix، تنظیم مسیر در .zshrc/.bashrc/.bash_profile و غیره کار نمی‌کند.\nباید مسیر را در فایل پیکربندی شل تنظیم کنید.'; @override String get download => 'دانلود'; @@ -1335,7 +1367,8 @@ class AppLocalizationsFa extends AppLocalizations { String get edit_port => 'ویرایش پورت'; @override - String get port_helper_msg => 'پیش‌فرض -1 است که نشان‌دهنده یک عدد تصادفی است. اگر فایروال شما پیکربندی شده است، توصیه می‌شود این را تنظیم کنید.'; + String get port_helper_msg => + 'پیش‌فرض -1 است که نشان‌دهنده یک عدد تصادفی است. اگر فایروال شما پیکربندی شده است، توصیه می‌شود این را تنظیم کنید.'; @override String connect_request(Object client) { @@ -1343,5 +1376,6 @@ class AppLocalizationsFa extends AppLocalizations { } @override - String get connection_request_denied => 'اتصال رد شد. کاربر دسترسی را رد کرد.'; + String get connection_request_denied => + 'اتصال رد شد. کاربر دسترسی را رد کرد.'; } diff --git a/lib/l10n/generated/app_localizations_fi.dart b/lib/l10n/generated/app_localizations_fi.dart index 5131fa41..6fbd9cb4 100644 --- a/lib/l10n/generated/app_localizations_fi.dart +++ b/lib/l10n/generated/app_localizations_fi.dart @@ -407,7 +407,8 @@ class AppLocalizationsFi extends AppLocalizations { String get layout_mode => 'Asettelutila'; @override - String get override_layout_settings => 'Jätä reagoiva asettelutila huomioimatta'; + String get override_layout_settings => + 'Jätä reagoiva asettelutila huomioimatta'; @override String get adaptive => 'Mukautuva'; @@ -437,7 +438,8 @@ class AppLocalizationsFi extends AppLocalizations { String get sync_album_color => 'Synkronoi albumin väri'; @override - String get sync_album_color_description => 'Käyttää albumin kansitaiteen vallitsevaa väirä korostuvärinä'; + String get sync_album_color_description => + 'Käyttää albumin kansitaiteen vallitsevaa väirä korostuvärinä'; @override String get playback => 'Toisto'; @@ -455,7 +457,8 @@ class AppLocalizationsFi extends AppLocalizations { String get pre_download_play => 'Esilataa ja soita'; @override - String get pre_download_play_description => 'Audion suoratoiston sijaan, lataa tavut ja soita ne (Suositeltu korkeamman kaistanleveyden käyttäjille)'; + String get pre_download_play_description => + 'Audion suoratoiston sijaan, lataa tavut ja soita ne (Suositeltu korkeamman kaistanleveyden käyttäjille)'; @override String get skip_non_music => 'Ohita ei-musiikki kohdat (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsFi extends AppLocalizations { String get blacklist_description => 'Mustalistat kappaleet aja artistit'; @override - String get wait_for_download_to_finish => 'Odota nykyisen latauksen lopetteluun'; + String get wait_for_download_to_finish => + 'Odota nykyisen latauksen lopetteluun'; @override String get desktop => 'Työpöytä'; @@ -500,7 +504,8 @@ class AppLocalizationsFi extends AppLocalizations { String get please_sponsor => 'Sponsoroi/Lahjoita, kiitos'; @override - String get spotube_description => 'Spotube, kevyt, cross-platform, vapaa-kaikille spotify clientti'; + String get spotube_description => + 'Spotube, kevyt, cross-platform, vapaa-kaikille spotify clientti'; @override String get version => 'Versio'; @@ -532,7 +537,8 @@ class AppLocalizationsFi extends AppLocalizations { String get license => 'Lisenssi'; @override - String get credentials_will_not_be_shared_disclaimer => 'Älä huoli, tunnuksiasi ei talleteta tai jaeta kenenkään kanssa'; + String get credentials_will_not_be_shared_disclaimer => + 'Älä huoli, tunnuksiasi ei talleteta tai jaeta kenenkään kanssa'; @override String get know_how_to_login => 'Etkö tiedä miten tehdä tämä?'; @@ -576,19 +582,23 @@ class AppLocalizationsFi extends AppLocalizations { String get piped_instance => 'Johdettu palvelinesiintymä'; @override - String get piped_description => 'Johdettu palvelinesiintymä Kappale täsmäyksiin'; + String get piped_description => + 'Johdettu palvelinesiintymä Kappale täsmäyksiin'; @override - String get piped_warning => 'Jotkut niistä eivät toimi hyvin, käytä siis omalla vastuullasi'; + String get piped_warning => + 'Jotkut niistä eivät toimi hyvin, käytä siis omalla vastuullasi'; @override String get invidious_instance => 'Invidious-palvelinesiintymä'; @override - String get invidious_description => 'Invidious-palvelinesiintymä raitojen yhteensovittamiseen'; + String get invidious_description => + 'Invidious-palvelinesiintymä raitojen yhteensovittamiseen'; @override - String get invidious_warning => 'Jotkin esiintymät eivät välttämättä toimi hyvin. Käytä omalla vastuullasi'; + String get invidious_warning => + 'Jotkin esiintymät eivät välttämättä toimi hyvin. Käytä omalla vastuullasi'; @override String get generate => 'Luo'; @@ -605,7 +615,8 @@ class AppLocalizationsFi extends AppLocalizations { String get skip_download_tracks => 'Ohita ladattujen laulujen lataaminen'; @override - String get do_you_want_to_replace => 'Haluatko korvata olemassa olevan kappaleen??'; + String get do_you_want_to_replace => + 'Haluatko korvata olemassa olevan kappaleen??'; @override String get replace => 'Korvaa'; @@ -711,22 +722,28 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get download_warning => 'Jos lataat kaikki laulut kerrällä olet selkeästi Piratoimassa ja aiheuttamassa vahinkoa musiikin luovaan yhteiskuntaan. Toivottavasti olet tietoinen tästä. Yritä aina kunnioittaa ja tukea Artistin kovaa työtä.'; + String get download_warning => + 'Jos lataat kaikki laulut kerrällä olet selkeästi Piratoimassa ja aiheuttamassa vahinkoa musiikin luovaan yhteiskuntaan. Toivottavasti olet tietoinen tästä. Yritä aina kunnioittaa ja tukea Artistin kovaa työtä.'; @override - String get download_ip_ban_warning => 'BTW, YouTube voi estää IP-Osoitteesi tavallista liiallisten latauspyyntöjen takia. IP-Osoitteen esto tarkoittaa sitä, ettet voi käyttää YouTubea (vaikka olisit kirjautunut) vähintään 2-3kk aikana kyseiseltä laitteelta. Spotube ei kanna yhtään vastuuta jos se tapahtuu.'; + String get download_ip_ban_warning => + 'BTW, YouTube voi estää IP-Osoitteesi tavallista liiallisten latauspyyntöjen takia. IP-Osoitteen esto tarkoittaa sitä, ettet voi käyttää YouTubea (vaikka olisit kirjautunut) vähintään 2-3kk aikana kyseiseltä laitteelta. Spotube ei kanna yhtään vastuuta jos se tapahtuu.'; @override - String get by_clicking_accept_terms => 'Painamalla \'hyväksy\' hyväksyt seuraaviin ehtoihin:'; + String get by_clicking_accept_terms => + 'Painamalla \'hyväksy\' hyväksyt seuraaviin ehtoihin:'; @override - String get download_agreement_1 => 'Tiedän että Piratoin musiikkia. Olen paha.'; + String get download_agreement_1 => + 'Tiedän että Piratoin musiikkia. Olen paha.'; @override - String get download_agreement_2 => 'Tuen Artisteja silloin kun pystyn, ja teen tämän vain koska minulla ei ole rahaa ostaa heidän taidetta'; + String get download_agreement_2 => + 'Tuen Artisteja silloin kun pystyn, ja teen tämän vain koska minulla ei ole rahaa ostaa heidän taidetta'; @override - String get download_agreement_3 => 'Ymmärrän että minun YouTube voi estää IP-Osoitteeni ja en pidä Spotubea tai omistajiinsa/avustajia vastuullisena mistään omista teoistsani'; + String get download_agreement_3 => + 'Ymmärrän että minun YouTube voi estää IP-Osoitteeni ja en pidä Spotubea tai omistajiinsa/avustajia vastuullisena mistään omista teoistsani'; @override String get decline => 'Hylkää'; @@ -807,7 +824,8 @@ class AppLocalizationsFi extends AppLocalizations { String get failed_to_encrypt => 'Salaaminen epäonnistui'; @override - String get encryption_failed_warning => 'Spotube käyttää salausta tallentaakseen tietosi, mutta epäonnistui, joten se palaa epäturvalliseen tallennukseen\nJos käytät Linuxia, varmista että sinulla on turvallisuuspalvelu (gnome-keyring, kde-wallet, keepassxc jne) asennettu'; + String get encryption_failed_warning => + 'Spotube käyttää salausta tallentaakseen tietosi, mutta epäonnistui, joten se palaa epäturvalliseen tallennukseen\nJos käytät Linuxia, varmista että sinulla on turvallisuuspalvelu (gnome-keyring, kde-wallet, keepassxc jne) asennettu'; @override String get querying_info => 'Hankitaan tietoa...'; @@ -905,7 +923,8 @@ class AppLocalizationsFi extends AppLocalizations { String get friends => 'Kaverit'; @override - String get no_lyrics_available => 'Anteeksi, emme löytäneet lyriikoita tälle laululle'; + String get no_lyrics_available => + 'Anteeksi, emme löytäneet lyriikoita tälle laululle'; @override String get start_a_radio => 'Aloita Radio'; @@ -914,7 +933,8 @@ class AppLocalizationsFi extends AppLocalizations { String get how_to_start_radio => 'Kuinka haluat aloittaa radion?'; @override - String get replace_queue_question => 'Haluatko korvata nykyisen jonon vai lisätä siihen?'; + String get replace_queue_question => + 'Haluatko korvata nykyisen jonon vai lisätä siihen?'; @override String get endless_playback => 'Loputon toisto'; @@ -923,7 +943,8 @@ class AppLocalizationsFi extends AppLocalizations { String get delete_playlist => 'Poista soittolista'; @override - String get delete_playlist_confirmation => 'Oletko varma että haluat poistaa tämän soittolistan?'; + String get delete_playlist_confirmation => + 'Oletko varma että haluat poistaa tämän soittolistan?'; @override String get local_tracks => 'Paikalliset kappaleet'; @@ -950,13 +971,15 @@ class AppLocalizationsFi extends AppLocalizations { String get youtube_source_description => 'Suositeltu ja toimii parhaiten.'; @override - String get piped_source_description => 'Tuntuuko vapaalta? Sama kuin YouTube mutta paljon vapautta'; + String get piped_source_description => + 'Tuntuuko vapaalta? Sama kuin YouTube mutta paljon vapautta'; @override String get jiosaavn_source_description => 'Paras Etelä-Aasian alueelle.'; @override - String get invidious_source_description => 'Samankaltainen kuin Piped, mutta korkeammalla saatavuudella'; + String get invidious_source_description => + 'Samankaltainen kuin Piped, mutta korkeammalla saatavuudella'; @override String highest_quality(Object quality) { @@ -967,13 +990,15 @@ class AppLocalizationsFi extends AppLocalizations { String get select_audio_source => 'Valitse äänilähde'; @override - String get endless_playback_description => 'Lisää automaattisesti uusia lauluja\njonon perään'; + String get endless_playback_description => + 'Lisää automaattisesti uusia lauluja\njonon perään'; @override String get choose_your_region => 'Valitse alueesi'; @override - String get choose_your_region_description => 'Tämä auttaa Spotube näyttämään sinulle oikeaa sisältöä\nsijaintiasi varten.'; + String get choose_your_region_description => + 'Tämä auttaa Spotube näyttämään sinulle oikeaa sisältöä\nsijaintiasi varten.'; @override String get choose_your_language => 'Valitse kielesi'; @@ -982,7 +1007,8 @@ class AppLocalizationsFi extends AppLocalizations { String get help_project_grow => 'Auta tätä projektia kasvamaan'; @override - String get help_project_grow_description => 'Spotube projekti minkä lähdekoodi on julkisesti saatavilla. Voit autta tätä projektia kasvamaan muutoksilla, ilmoittamalla bugeista, tai ehdottamalla uusia ominaisuuksia.'; + String get help_project_grow_description => + 'Spotube projekti minkä lähdekoodi on julkisesti saatavilla. Voit autta tätä projektia kasvamaan muutoksilla, ilmoittamalla bugeista, tai ehdottamalla uusia ominaisuuksia.'; @override String get contribute_on_github => 'Auta GitHub:ssa'; @@ -1076,7 +1102,8 @@ class AppLocalizationsFi extends AppLocalizations { String get choose_the_device => 'Valitse laite:'; @override - String get multiple_device_connected => 'Useita laitteita on kytketty.\nValitse laite, jossa haluat toiminnon suorittaa'; + String get multiple_device_connected => + 'Useita laitteita on kytketty.\nValitse laite, jossa haluat toiminnon suorittaa'; @override String get nothing_found => 'Ei tuloksia'; @@ -1149,7 +1176,8 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Suoratoiston maksut (hypoteettinen)'; + String get streaming_fees_hypothetical => + 'Suoratoiston maksut (hypoteettinen)'; @override String get minutes_listened => 'Kuunneltuja minuutteja'; @@ -1171,7 +1199,8 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1249,8 @@ class AppLocalizationsFi extends AppLocalizations { String get webview_not_found => 'Webview ei löydy'; @override - String get webview_not_found_description => 'Laitteellasi ei ole asennettua Webview-ajonaikaa.\nJos se on asennettu, varmista, että se on environment PATH:ssa\n\nAsennuksen jälkeen käynnistä sovellus uudelleen'; + String get webview_not_found_description => + 'Laitteellasi ei ole asennettua Webview-ajonaikaa.\nJos se on asennettu, varmista, että se on environment PATH:ssa\n\nAsennuksen jälkeen käynnistä sovellus uudelleen'; @override String get unsupported_platform => 'Ei tuettu alusta'; @@ -1281,19 +1311,22 @@ class AppLocalizationsFi extends AppLocalizations { String get view_all => 'Näytä kaikki'; @override - String get no_tracks_added_yet => 'Näyttää siltä, että et ole lisännyt vielä mitään kappaleita.'; + String get no_tracks_added_yet => + 'Näyttää siltä, että et ole lisännyt vielä mitään kappaleita.'; @override String get no_tracks => 'Näyttää siltä, että täällä ei ole kappaleita.'; @override - String get no_tracks_listened_yet => 'Näyttää siltä, että et ole kuunnellut mitään vielä.'; + String get no_tracks_listened_yet => + 'Näyttää siltä, että et ole kuunnellut mitään vielä.'; @override String get not_following_artists => 'Et seuraa yhtään artistia.'; @override - String get no_favorite_albums_yet => 'Näyttää siltä, että et ole lisännyt yhtään albumia suosikkeihisi.'; + String get no_favorite_albums_yet => + 'Näyttää siltä, että et ole lisännyt yhtään albumia suosikkeihisi.'; @override String get no_logs_found => 'Ei lokitietoja löydetty'; @@ -1317,7 +1350,8 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/unix-tyyppisissä käyttöjärjestelmissä polun asettaminen .zshrc/.bashrc/.bash_profile jne. ei toimi.\nSinun täytyy asettaa polku shellin asetustiedostoon.'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/unix-tyyppisissä käyttöjärjestelmissä polun asettaminen .zshrc/.bashrc/.bash_profile jne. ei toimi.\nSinun täytyy asettaa polku shellin asetustiedostoon.'; @override String get download => 'Lataa'; @@ -1335,7 +1369,8 @@ class AppLocalizationsFi extends AppLocalizations { String get edit_port => 'Muokkaa porttia'; @override - String get port_helper_msg => 'Oletusarvo on -1, mikä tarkoittaa satunnaista numeroa. Jos sinulla on palomuuri määritetty, tämän asettamista suositellaan.'; + String get port_helper_msg => + 'Oletusarvo on -1, mikä tarkoittaa satunnaista numeroa. Jos sinulla on palomuuri määritetty, tämän asettamista suositellaan.'; @override String connect_request(Object client) { @@ -1343,5 +1378,6 @@ class AppLocalizationsFi extends AppLocalizations { } @override - String get connection_request_denied => 'Yhteys evätty. Käyttäjä eväsi pääsyn.'; + String get connection_request_denied => + 'Yhteys evätty. Käyttäjä eväsi pääsyn.'; } diff --git a/lib/l10n/generated/app_localizations_fr.dart b/lib/l10n/generated/app_localizations_fr.dart index f1efd200..d19a31f8 100644 --- a/lib/l10n/generated/app_localizations_fr.dart +++ b/lib/l10n/generated/app_localizations_fr.dart @@ -27,7 +27,8 @@ class AppLocalizationsFr extends AppLocalizations { String get settings => 'Paramètres'; @override - String get genre_categories_filter => 'Filtrer les catégories ou les genres...'; + String get genre_categories_filter => + 'Filtrer les catégories ou les genres...'; @override String get genre => 'Genre'; @@ -185,7 +186,8 @@ class AppLocalizationsFr extends AppLocalizations { String get follow => 'S\'abonner'; @override - String get artist_url_copied => 'URL de l\'artiste copiée dans le presse-papiers'; + String get artist_url_copied => + 'URL de l\'artiste copiée dans le presse-papiers'; @override String added_to_queue(Object tracks) { @@ -315,7 +317,8 @@ class AppLocalizationsFr extends AppLocalizations { String get shuffle_playlist => 'Lecture aléatoire de la liste de lecture'; @override - String get unshuffle_playlist => 'Annuler la lecture aléatoire de la liste de lecture'; + String get unshuffle_playlist => + 'Annuler la lecture aléatoire de la liste de lecture'; @override String get previous_track => 'Piste précédente'; @@ -356,7 +359,8 @@ class AppLocalizationsFr extends AppLocalizations { String get clear_all => 'Tout effacer'; @override - String get show_hide_ui_on_hover => 'Afficher/Masquer l\'interface utilisateur au survol'; + String get show_hide_ui_on_hover => + 'Afficher/Masquer l\'interface utilisateur au survol'; @override String get always_on_top => 'Toujours au-dessus'; @@ -407,7 +411,8 @@ class AppLocalizationsFr extends AppLocalizations { String get layout_mode => 'Mode de mise en page'; @override - String get override_layout_settings => 'Remplacer les paramètres de mise en page adaptative'; + String get override_layout_settings => + 'Remplacer les paramètres de mise en page adaptative'; @override String get adaptive => 'Adaptatif'; @@ -437,7 +442,8 @@ class AppLocalizationsFr extends AppLocalizations { String get sync_album_color => 'Synchroniser la couleur de l\'album'; @override - String get sync_album_color_description => 'Utilise la couleur dominante de l\'art de l\'album comme couleur d\'accentuation'; + String get sync_album_color_description => + 'Utilise la couleur dominante de l\'art de l\'album comme couleur d\'accentuation'; @override String get playback => 'Lecture'; @@ -455,16 +461,19 @@ class AppLocalizationsFr extends AppLocalizations { String get pre_download_play => 'Pré-télécharger et lire'; @override - String get pre_download_play_description => 'Au lieu de diffuser de l\'audio, téléchargez les octets et lisez-les à la place (recommandé pour les utilisateurs à bande passante élevée)'; + String get pre_download_play_description => + 'Au lieu de diffuser de l\'audio, téléchargez les octets et lisez-les à la place (recommandé pour les utilisateurs à bande passante élevée)'; @override - String get skip_non_music => 'Ignorer les segments non musicaux (SponsorBlock)'; + String get skip_non_music => + 'Ignorer les segments non musicaux (SponsorBlock)'; @override String get blacklist_description => 'Pistes et artistes en liste noire'; @override - String get wait_for_download_to_finish => 'Veuillez attendre la fin du téléchargement en cours'; + String get wait_for_download_to_finish => + 'Veuillez attendre la fin du téléchargement en cours'; @override String get desktop => 'Bureau'; @@ -500,7 +509,8 @@ class AppLocalizationsFr extends AppLocalizations { String get please_sponsor => 'S\'il vous plaît Sponsoriser/Donner'; @override - String get spotube_description => 'Spotube, un client Spotify léger, multiplateforme et gratuit pour tous'; + String get spotube_description => + 'Spotube, un client Spotify léger, multiplateforme et gratuit pour tous'; @override String get version => 'Version'; @@ -532,7 +542,8 @@ class AppLocalizationsFr extends AppLocalizations { String get license => 'Licence'; @override - String get credentials_will_not_be_shared_disclaimer => 'Ne vous inquiétez pas, vos identifiants ne seront ni collectés ni partagés avec qui que ce soit'; + String get credentials_will_not_be_shared_disclaimer => + 'Ne vous inquiétez pas, vos identifiants ne seront ni collectés ni partagés avec qui que ce soit'; @override String get know_how_to_login => 'Vous ne savez pas comment faire?'; @@ -576,19 +587,23 @@ class AppLocalizationsFr extends AppLocalizations { String get piped_instance => 'Instance pipée'; @override - String get piped_description => 'L\'instance de serveur Piped à utiliser pour la correspondance des pistes'; + String get piped_description => + 'L\'instance de serveur Piped à utiliser pour la correspondance des pistes'; @override - String get piped_warning => 'Certaines d\'entre elles peuvent ne pas fonctionner correctement. Alors utilisez à vos risques et périls'; + String get piped_warning => + 'Certaines d\'entre elles peuvent ne pas fonctionner correctement. Alors utilisez à vos risques et périls'; @override String get invidious_instance => 'Instance de serveur Invidious'; @override - String get invidious_description => 'L\'instance de serveur Invidious à utiliser pour la correspondance de pistes'; + String get invidious_description => + 'L\'instance de serveur Invidious à utiliser pour la correspondance de pistes'; @override - String get invidious_warning => 'Certaines instances pourraient ne pas bien fonctionner. À utiliser à vos risques et périls'; + String get invidious_warning => + 'Certaines instances pourraient ne pas bien fonctionner. À utiliser à vos risques et périls'; @override String get generate => 'Générer'; @@ -599,13 +614,16 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Remplacer toutes les pistes téléchargées'; + String get replace_downloaded_tracks => + 'Remplacer toutes les pistes téléchargées'; @override - String get skip_download_tracks => 'Ignorer le téléchargement de toutes les pistes téléchargées'; + String get skip_download_tracks => + 'Ignorer le téléchargement de toutes les pistes téléchargées'; @override - String get do_you_want_to_replace => 'Voulez-vous remplacer la piste existante ?'; + String get do_you_want_to_replace => + 'Voulez-vous remplacer la piste existante ?'; @override String get replace => 'Remplacer'; @@ -703,7 +721,8 @@ class AppLocalizationsFr extends AppLocalizations { String get are_you_sure => 'Êtes-vous sûr(e) ?'; @override - String get generating_playlist => 'Génération de votre playlist personnalisée en cours...'; + String get generating_playlist => + 'Génération de votre playlist personnalisée en cours...'; @override String selected_count_tracks(Object count) { @@ -711,22 +730,28 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get download_warning => 'Si vous téléchargez toutes les pistes en vrac, vous violez clairement les droits d\'auteur de la musique et vous causez des dommages à la société créative de la musique. J\'espère que vous en êtes conscient. Essayez toujours de respecter et de soutenir le travail acharné des artistes.'; + String get download_warning => + 'Si vous téléchargez toutes les pistes en vrac, vous violez clairement les droits d\'auteur de la musique et vous causez des dommages à la société créative de la musique. J\'espère que vous en êtes conscient. Essayez toujours de respecter et de soutenir le travail acharné des artistes.'; @override - String get download_ip_ban_warning => 'Au fait, votre adresse IP peut être bloquée sur YouTube en raison d\'une demande excessive de téléchargements par rapport à la normale. Le blocage de l\'IP signifie que vous ne pourrez pas utiliser YouTube (même si vous êtes connecté) pendant au moins 2 à 3 mois à partir de cet appareil IP. Et Spotube ne peut être tenu responsable si cela se produit.'; + String get download_ip_ban_warning => + 'Au fait, votre adresse IP peut être bloquée sur YouTube en raison d\'une demande excessive de téléchargements par rapport à la normale. Le blocage de l\'IP signifie que vous ne pourrez pas utiliser YouTube (même si vous êtes connecté) pendant au moins 2 à 3 mois à partir de cet appareil IP. Et Spotube ne peut être tenu responsable si cela se produit.'; @override - String get by_clicking_accept_terms => 'En cliquant sur \'accepter\', vous acceptez les conditions suivantes :'; + String get by_clicking_accept_terms => + 'En cliquant sur \'accepter\', vous acceptez les conditions suivantes :'; @override - String get download_agreement_1 => 'Je sais que je pirate de la musique. Je suis méchant(e).'; + String get download_agreement_1 => + 'Je sais que je pirate de la musique. Je suis méchant(e).'; @override - String get download_agreement_2 => 'Je soutiendrai l\'artiste autant que possible et je ne fais cela que parce que je n\'ai pas d\'argent pour acheter leur art.'; + String get download_agreement_2 => + 'Je soutiendrai l\'artiste autant que possible et je ne fais cela que parce que je n\'ai pas d\'argent pour acheter leur art.'; @override - String get download_agreement_3 => 'Je suis parfaitement conscient(e) que mon adresse IP peut être bloquée sur YouTube et je ne tiens pas Spotube ni ses propriétaires/contributeurs responsables de tout accident causé par mon action actuelle.'; + String get download_agreement_3 => + 'Je suis parfaitement conscient(e) que mon adresse IP peut être bloquée sur YouTube et je ne tiens pas Spotube ni ses propriétaires/contributeurs responsables de tout accident causé par mon action actuelle.'; @override String get decline => 'Refuser'; @@ -807,7 +832,8 @@ class AppLocalizationsFr extends AppLocalizations { String get failed_to_encrypt => 'Échec de la cryptage'; @override - String get encryption_failed_warning => 'Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d\'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc'; + String get encryption_failed_warning => + 'Spotube utilise le cryptage pour stocker vos données en toute sécurité. Mais cela a échoué. Il basculera donc vers un stockage non sécurisé\nSi vous utilisez Linux, assurez-vous d\'avoir installé des services secrets tels que gnome-keyring, kde-wallet et keepassxc'; @override String get querying_info => 'Interrogation des info...'; @@ -905,7 +931,8 @@ class AppLocalizationsFr extends AppLocalizations { String get friends => 'Amis'; @override - String get no_lyrics_available => 'Désolé, impossible de trouver les paroles de cette piste'; + String get no_lyrics_available => + 'Désolé, impossible de trouver les paroles de cette piste'; @override String get start_a_radio => 'Démarrer une radio'; @@ -914,7 +941,8 @@ class AppLocalizationsFr extends AppLocalizations { String get how_to_start_radio => 'Comment voulez-vous démarrer la radio ?'; @override - String get replace_queue_question => 'Voulez-vous remplacer la file d\'attente actuelle ou y ajouter ?'; + String get replace_queue_question => + 'Voulez-vous remplacer la file d\'attente actuelle ou y ajouter ?'; @override String get endless_playback => 'Lecture sans fin'; @@ -923,7 +951,8 @@ class AppLocalizationsFr extends AppLocalizations { String get delete_playlist => 'Supprimer la playlist'; @override - String get delete_playlist_confirmation => 'Êtes-vous sûr de vouloir supprimer cette playlist ?'; + String get delete_playlist_confirmation => + 'Êtes-vous sûr de vouloir supprimer cette playlist ?'; @override String get local_tracks => 'Titres locaux'; @@ -941,7 +970,8 @@ class AppLocalizationsFr extends AppLocalizations { String get freedom_of_music => '“Liberté de la musique”'; @override - String get freedom_of_music_palm => '“Liberté de la musique dans la paume de votre main”'; + String get freedom_of_music_palm => + '“Liberté de la musique dans la paume de votre main”'; @override String get get_started => 'Commençons'; @@ -950,13 +980,16 @@ class AppLocalizationsFr extends AppLocalizations { String get youtube_source_description => 'Recommandé et fonctionne mieux.'; @override - String get piped_source_description => 'Vous vous sentez libre ? Comme YouTube mais beaucoup plus gratuit.'; + String get piped_source_description => + 'Vous vous sentez libre ? Comme YouTube mais beaucoup plus gratuit.'; @override - String get jiosaavn_source_description => 'Le meilleur pour la région d\'Asie du Sud.'; + String get jiosaavn_source_description => + 'Le meilleur pour la région d\'Asie du Sud.'; @override - String get invidious_source_description => 'Similaire à Piped mais avec une meilleure disponibilité'; + String get invidious_source_description => + 'Similaire à Piped mais avec une meilleure disponibilité'; @override String highest_quality(Object quality) { @@ -967,13 +1000,15 @@ class AppLocalizationsFr extends AppLocalizations { String get select_audio_source => 'Sélectionner la source audio'; @override - String get endless_playback_description => 'Ajouter automatiquement de nouvelles chansons à la fin de la file d\'attente'; + String get endless_playback_description => + 'Ajouter automatiquement de nouvelles chansons à la fin de la file d\'attente'; @override String get choose_your_region => 'Choisissez votre région'; @override - String get choose_your_region_description => 'Cela aidera Spotube à vous montrer le bon contenu pour votre emplacement.'; + String get choose_your_region_description => + 'Cela aidera Spotube à vous montrer le bon contenu pour votre emplacement.'; @override String get choose_your_language => 'Choisissez votre langue'; @@ -982,7 +1017,8 @@ class AppLocalizationsFr extends AppLocalizations { String get help_project_grow => 'Aidez ce projet à grandir'; @override - String get help_project_grow_description => 'Spotube est un projet open-source. Vous pouvez aider ce projet à grandir en contribuant au projet, en signalant des bugs ou en suggérant de nouvelles fonctionnalités.'; + String get help_project_grow_description => + 'Spotube est un projet open-source. Vous pouvez aider ce projet à grandir en contribuant au projet, en signalant des bugs ou en suggérant de nouvelles fonctionnalités.'; @override String get contribute_on_github => 'Contribuer sur GitHub'; @@ -997,7 +1033,8 @@ class AppLocalizationsFr extends AppLocalizations { String get enable_connect => 'Activer la connexion'; @override - String get enable_connect_description => 'Contrôlez Spotube depuis d\'autres appareils'; + String get enable_connect_description => + 'Contrôlez Spotube depuis d\'autres appareils'; @override String get devices => 'Appareils'; @@ -1076,7 +1113,8 @@ class AppLocalizationsFr extends AppLocalizations { String get choose_the_device => 'Choisissez l\'appareil:'; @override - String get multiple_device_connected => 'Plusieurs appareils sont connectés.\nChoisissez l\'appareil sur lequel vous souhaitez effectuer cette action'; + String get multiple_device_connected => + 'Plusieurs appareils sont connectés.\nChoisissez l\'appareil sur lequel vous souhaitez effectuer cette action'; @override String get nothing_found => 'Rien trouvé'; @@ -1149,7 +1187,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Frais de streaming (hypothétiques)'; + String get streaming_fees_hypothetical => + 'Frais de streaming (hypothétiques)'; @override String get minutes_listened => 'Minutes écoutées'; @@ -1171,7 +1210,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1260,8 @@ class AppLocalizationsFr extends AppLocalizations { String get webview_not_found => 'Webview non trouvé'; @override - String get webview_not_found_description => 'Aucun environnement d\'exécution Webview installé sur votre appareil.\nSi c\'est installé, assurez-vous qu\'il soit dans le environment PATH\n\nAprès l\'installation, redémarrez l\'application'; + String get webview_not_found_description => + 'Aucun environnement d\'exécution Webview installé sur votre appareil.\nSi c\'est installé, assurez-vous qu\'il soit dans le environment PATH\n\nAprès l\'installation, redémarrez l\'application'; @override String get unsupported_platform => 'Plateforme non prise en charge'; @@ -1252,7 +1293,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get export_cache_confirmation => 'Voulez-vous exporter ces fichiers vers'; + String get export_cache_confirmation => + 'Voulez-vous exporter ces fichiers vers'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1323,22 @@ class AppLocalizationsFr extends AppLocalizations { String get view_all => 'Voir tout'; @override - String get no_tracks_added_yet => 'Il semble que vous n\'avez encore ajouté aucun morceau.'; + String get no_tracks_added_yet => + 'Il semble que vous n\'avez encore ajouté aucun morceau.'; @override String get no_tracks => 'Il semble qu\'il n\'y ait pas de morceaux ici.'; @override - String get no_tracks_listened_yet => 'Il semble que vous n\'avez encore rien écouté.'; + String get no_tracks_listened_yet => + 'Il semble que vous n\'avez encore rien écouté.'; @override String get not_following_artists => 'Vous ne suivez aucun artiste.'; @override - String get no_favorite_albums_yet => 'Il semble que vous n\'ayez encore ajouté aucun album à vos favoris.'; + String get no_favorite_albums_yet => + 'Il semble que vous n\'ayez encore ajouté aucun album à vos favoris.'; @override String get no_logs_found => 'Aucun log trouvé'; @@ -1317,7 +1362,8 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'Dans macOS/Linux/les systèmes d\'exploitation similaires à Unix, définir le chemin dans .zshrc/.bashrc/.bash_profile etc. ne fonctionnera pas.\nVous devez définir le chemin dans le fichier de configuration du shell.'; + String get youtube_engine_unix_issue_message => + 'Dans macOS/Linux/les systèmes d\'exploitation similaires à Unix, définir le chemin dans .zshrc/.bashrc/.bash_profile etc. ne fonctionnera pas.\nVous devez définir le chemin dans le fichier de configuration du shell.'; @override String get download => 'Télécharger'; @@ -1335,7 +1381,8 @@ class AppLocalizationsFr extends AppLocalizations { String get edit_port => 'Modifier le port'; @override - String get port_helper_msg => 'La valeur par défaut est -1, ce qui indique un nombre aléatoire. Si vous avez configuré un pare-feu, il est recommandé de le définir.'; + String get port_helper_msg => + 'La valeur par défaut est -1, ce qui indique un nombre aléatoire. Si vous avez configuré un pare-feu, il est recommandé de le définir.'; @override String connect_request(Object client) { @@ -1343,5 +1390,6 @@ class AppLocalizationsFr extends AppLocalizations { } @override - String get connection_request_denied => 'Connection denied. User denied access.'; + String get connection_request_denied => + 'Connection denied. User denied access.'; } diff --git a/lib/l10n/generated/app_localizations_hi.dart b/lib/l10n/generated/app_localizations_hi.dart index bacebff6..a3f56a25 100644 --- a/lib/l10n/generated/app_localizations_hi.dart +++ b/lib/l10n/generated/app_localizations_hi.dart @@ -407,7 +407,8 @@ class AppLocalizationsHi extends AppLocalizations { String get layout_mode => 'लेआउट मोड'; @override - String get override_layout_settings => 'ओवरराइड रेस्पॉन्सिव लेआउट मोड सेटिंग्स'; + String get override_layout_settings => + 'ओवरराइड रेस्पॉन्सिव लेआउट मोड सेटिंग्स'; @override String get adaptive => 'अनुकूल'; @@ -437,7 +438,8 @@ class AppLocalizationsHi extends AppLocalizations { String get sync_album_color => 'एल्बम का रंग सिंक करें'; @override - String get sync_album_color_description => 'एल्बम कला का प्रधान रंग एक्सेंट रंग के रूप में उपयोग किया जाता है'; + String get sync_album_color_description => + 'एल्बम कला का प्रधान रंग एक्सेंट रंग के रूप में उपयोग किया जाता है'; @override String get playback => 'प्लेबैक'; @@ -455,16 +457,19 @@ class AppLocalizationsHi extends AppLocalizations { String get pre_download_play => 'पूर्वावत डाउनलोड और प्ले करें'; @override - String get pre_download_play_description => 'ऑडियो स्ट्रीमिंग की बजाय बाइट्स डाउनलोड करें और बजाय में प्ले करें (उच्च बैंडविड्थ उपयोगकर्ताओं के लिए सिफारिश किया जाता है)'; + String get pre_download_play_description => + 'ऑडियो स्ट्रीमिंग की बजाय बाइट्स डाउनलोड करें और बजाय में प्ले करें (उच्च बैंडविड्थ उपयोगकर्ताओं के लिए सिफारिश किया जाता है)'; @override - String get skip_non_music => 'गाने के अलावा सेगमेंट्स को छोड़ें (स्पॉन्सरब्लॉक)'; + String get skip_non_music => + 'गाने के अलावा सेगमेंट्स को छोड़ें (स्पॉन्सरब्लॉक)'; @override String get blacklist_description => 'ब्लैकलिस्ट में शामिल ट्रैक और कलाकार'; @override - String get wait_for_download_to_finish => 'वर्तमान डाउनलोड समाप्त होने तक कृपया प्रतीक्षा करें'; + String get wait_for_download_to_finish => + 'वर्तमान डाउनलोड समाप्त होने तक कृपया प्रतीक्षा करें'; @override String get desktop => 'डेस्कटॉप'; @@ -500,7 +505,8 @@ class AppLocalizationsHi extends AppLocalizations { String get please_sponsor => 'कृपया स्पॉन्सर / डोनेट करें'; @override - String get spotube_description => 'Spotube, एक हल्का, सभी प्लेटफॉर्मों पर चलने वाला, मुफ्त स्पॉटिफाई क्लाइंट'; + String get spotube_description => + 'Spotube, एक हल्का, सभी प्लेटफॉर्मों पर चलने वाला, मुफ्त स्पॉटिफाई क्लाइंट'; @override String get version => 'संस्करण'; @@ -532,7 +538,8 @@ class AppLocalizationsHi extends AppLocalizations { String get license => 'लाइसेंस'; @override - String get credentials_will_not_be_shared_disclaimer => 'चिंता न करें, आपके क्रेडेंशियल किसी भी तरह से नहीं एकत्रित या साझा किए जाएंगे'; + String get credentials_will_not_be_shared_disclaimer => + 'चिंता न करें, आपके क्रेडेंशियल किसी भी तरह से नहीं एकत्रित या साझा किए जाएंगे'; @override String get know_how_to_login => 'इसे कैसे करें पता नहीं?'; @@ -579,16 +586,19 @@ class AppLocalizationsHi extends AppLocalizations { String get piped_description => 'पाइप किए गए सर्वर'; @override - String get piped_warning => 'गानों का मिलान करने के लिए उपयोग किए जाते हैं, हो सकता है कि उनमें से कुछ के साथ ठीक से काम न करें इसलिए अपने जोखिम पर उपयोग करें'; + String get piped_warning => + 'गानों का मिलान करने के लिए उपयोग किए जाते हैं, हो सकता है कि उनमें से कुछ के साथ ठीक से काम न करें इसलिए अपने जोखिम पर उपयोग करें'; @override String get invidious_instance => 'इन्विडियस सर्वर इंस्टेंस'; @override - String get invidious_description => 'ट्रैक मिलान के लिए इन्विडियस सर्वर इंस्टेंस'; + String get invidious_description => + 'ट्रैक मिलान के लिए इन्विडियस सर्वर इंस्टेंस'; @override - String get invidious_warning => 'कुछ इंस्टेंस अच्छी तरह से काम नहीं कर सकते। अपने जोखिम पर उपयोग करें'; + String get invidious_warning => + 'कुछ इंस्टेंस अच्छी तरह से काम नहीं कर सकते। अपने जोखिम पर उपयोग करें'; @override String get generate => 'उत्पन्न करें'; @@ -605,7 +615,8 @@ class AppLocalizationsHi extends AppLocalizations { String get skip_download_tracks => 'सभी डाउनलोड किए गए ट्रैक्स को छोड़ें'; @override - String get do_you_want_to_replace => 'क्या आप मौजूदा ट्रैक को बदलना चाहते हैं?'; + String get do_you_want_to_replace => + 'क्या आप मौजूदा ट्रैक को बदलना चाहते हैं?'; @override String get replace => 'बदलें'; @@ -711,22 +722,28 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get download_warning => 'यदि आप सभी ट्रैक्स को बल्क में डाउनलोड करते हैं, तो आप स्पष्ट रूप से संगीत की अवैध नकली बना रहे हैं और संगीत के रचनात्मक समाज को क्षति पहुंचा रहे हैं। मुझे आशा है कि आप इसके बारे में जागरूक हैं। हमेशा कोशिश करें कि कलाकार के मेहनत का सम्मान और समर्थन करें।'; + String get download_warning => + 'यदि आप सभी ट्रैक्स को बल्क में डाउनलोड करते हैं, तो आप स्पष्ट रूप से संगीत की अवैध नकली बना रहे हैं और संगीत के रचनात्मक समाज को क्षति पहुंचा रहे हैं। मुझे आशा है कि आप इसके बारे में जागरूक हैं। हमेशा कोशिश करें कि कलाकार के मेहनत का सम्मान और समर्थन करें।'; @override - String get download_ip_ban_warning => 'बाहरी डाउनलोड अनुरोधों के कारण आपका आईपी YouTube पर अधिक से अधिक ब्लॉक हो सकता है। आईपी ब्लॉक का अर्थ है कि आप उसी आईपी उपकरण से कम से कम 2-3 महीनों तक YouTube का उपयोग नहीं कर सकेंगे (यदि आप लॉग इन हैं तो भी)। और स्पोट्यूब किसी भी जिम्मेदारी को नहीं उठाता है अगर ऐसा कभी होता है।'; + String get download_ip_ban_warning => + 'बाहरी डाउनलोड अनुरोधों के कारण आपका आईपी YouTube पर अधिक से अधिक ब्लॉक हो सकता है। आईपी ब्लॉक का अर्थ है कि आप उसी आईपी उपकरण से कम से कम 2-3 महीनों तक YouTube का उपयोग नहीं कर सकेंगे (यदि आप लॉग इन हैं तो भी)। और स्पोट्यूब किसी भी जिम्मेदारी को नहीं उठाता है अगर ऐसा कभी होता है।'; @override - String get by_clicking_accept_terms => '\'स्वीकार\' पर क्लिक करके आप निम्नलिखित शर्तों से सहमत होते हैं:'; + String get by_clicking_accept_terms => + '\'स्वीकार\' पर क्लिक करके आप निम्नलिखित शर्तों से सहमत होते हैं:'; @override - String get download_agreement_1 => 'मुझे पता है कि मैं संगीत की अवैध नकली बना रहा हूं। मैं बुरा हूं'; + String get download_agreement_1 => + 'मुझे पता है कि मैं संगीत की अवैध नकली बना रहा हूं। मैं बुरा हूं'; @override - String get download_agreement_2 => 'मैं कलाकार का समर्थन करूंगा जहां भी मुझे संभव हो और मैं केवल इसल िए ऐसा कर रहा हूं क्योंकि मेरे पास उनकी कला खरीदने के लिए पैसे नहीं हैं।'; + String get download_agreement_2 => + 'मैं कलाकार का समर्थन करूंगा जहां भी मुझे संभव हो और मैं केवल इसल िए ऐसा कर रहा हूं क्योंकि मेरे पास उनकी कला खरीदने के लिए पैसे नहीं हैं।'; @override - String get download_agreement_3 => 'मैं पूरी तरह से जागरूक हूं कि मेरा आईपी YouTube पर ब्लॉक हो सकता है और मैं स्पोट्यूब या उसके मालिकों / सहयोगियों को किसी भी दुर्घटना के लिए जिम्मेदार नहीं मानता।'; + String get download_agreement_3 => + 'मैं पूरी तरह से जागरूक हूं कि मेरा आईपी YouTube पर ब्लॉक हो सकता है और मैं स्पोट्यूब या उसके मालिकों / सहयोगियों को किसी भी दुर्घटना के लिए जिम्मेदार नहीं मानता।'; @override String get decline => 'इनकार करें'; @@ -807,7 +824,8 @@ class AppLocalizationsHi extends AppLocalizations { String get failed_to_encrypt => 'एन्क्रिप्ट करने में विफल रहा'; @override - String get encryption_failed_warning => 'Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है'; + String get encryption_failed_warning => + 'Spotube आपके डेटा को सुरक्षित रूप से स्टोर करने के लिए एन्क्रिप्शन का उपयोग करता है। लेकिन इसमें विफल रहा। इसलिए, यह असुरक्षित स्टोरेज पर फॉलबैक करेगा\nयदि आप Linux का उपयोग कर रहे हैं, तो कृपया सुनिश्चित करें कि आपके पास gnome-keyring, kde-wallet, keepassxc आदि जैसी कोई सीक्रेट-सर्विस इंस्टॉल की गई है'; @override String get querying_info => 'जानकारी प्राप्त करना'; @@ -905,7 +923,8 @@ class AppLocalizationsHi extends AppLocalizations { String get friends => 'दोस्त'; @override - String get no_lyrics_available => 'क्षमा करें, इस ट्रैक के लिए गाने नहीं मिल सके'; + String get no_lyrics_available => + 'क्षमा करें, इस ट्रैक के लिए गाने नहीं मिल सके'; @override String get start_a_radio => 'रेडियो शुरू करें'; @@ -914,7 +933,8 @@ class AppLocalizationsHi extends AppLocalizations { String get how_to_start_radio => 'रेडियो कैसे शुरू करना चाहते हैं?'; @override - String get replace_queue_question => 'क्या आप वर्तमान कतार को बदलना चाहते हैं या इसे जोड़ना चाहते हैं?'; + String get replace_queue_question => + 'क्या आप वर्तमान कतार को बदलना चाहते हैं या इसे जोड़ना चाहते हैं?'; @override String get endless_playback => 'अंतहीन प्लेबैक'; @@ -923,7 +943,8 @@ class AppLocalizationsHi extends AppLocalizations { String get delete_playlist => 'प्लेलिस्ट हटाएं'; @override - String get delete_playlist_confirmation => 'क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं?'; + String get delete_playlist_confirmation => + 'क्या आप वाकई इस प्लेलिस्ट को हटाना चाहते हैं?'; @override String get local_tracks => 'स्थानीय ट्रैक्स'; @@ -947,16 +968,20 @@ class AppLocalizationsHi extends AppLocalizations { String get get_started => 'आइए शुरू करें'; @override - String get youtube_source_description => 'सिफारिश किया गया और सबसे अच्छा काम करता है।'; + String get youtube_source_description => + 'सिफारिश किया गया और सबसे अच्छा काम करता है।'; @override - String get piped_source_description => 'मुफ्त महसूस कर रहे हैं? YouTube के समान लेकिन काफी अधिक मुफ्त।'; + String get piped_source_description => + 'मुफ्त महसूस कर रहे हैं? YouTube के समान लेकिन काफी अधिक मुफ्त।'; @override - String get jiosaavn_source_description => 'दक्षिण एशियाई क्षेत्र के लिए सर्वोत्तम।'; + String get jiosaavn_source_description => + 'दक्षिण एशियाई क्षेत्र के लिए सर्वोत्तम।'; @override - String get invidious_source_description => 'पाइप्ड के समान, लेकिन अधिक उपलब्धता के साथ'; + String get invidious_source_description => + 'पाइप्ड के समान, लेकिन अधिक उपलब्धता के साथ'; @override String highest_quality(Object quality) { @@ -967,13 +992,15 @@ class AppLocalizationsHi extends AppLocalizations { String get select_audio_source => 'ऑडियो स्रोत चुनें'; @override - String get endless_playback_description => 'क्रमबद्ध कतार के अंत में नए गाने स्वचालित रूप से जोड़ें'; + String get endless_playback_description => + 'क्रमबद्ध कतार के अंत में नए गाने स्वचालित रूप से जोड़ें'; @override String get choose_your_region => 'अपना क्षेत्र चुनें'; @override - String get choose_your_region_description => 'यह Spotube को आपके स्थान के लिए सही सामग्री दिखाने में मदद करेगा।'; + String get choose_your_region_description => + 'यह Spotube को आपके स्थान के लिए सही सामग्री दिखाने में मदद करेगा।'; @override String get choose_your_language => 'अपनी भाषा चुनें'; @@ -982,7 +1009,8 @@ class AppLocalizationsHi extends AppLocalizations { String get help_project_grow => 'इस परियोजना को बढ़ावा दें'; @override - String get help_project_grow_description => 'Spotube एक ओपन सोर्स परियोजना है। आप इस परियोजना को योगदान देकर, बग रिपोर्ट करके या नई विशेषताओं का सुझाव देकर इस परियोजना को बढ़ा सकते हैं।'; + String get help_project_grow_description => + 'Spotube एक ओपन सोर्स परियोजना है। आप इस परियोजना को योगदान देकर, बग रिपोर्ट करके या नई विशेषताओं का सुझाव देकर इस परियोजना को बढ़ा सकते हैं।'; @override String get contribute_on_github => 'GitHub पर योगदान करें'; @@ -997,7 +1025,8 @@ class AppLocalizationsHi extends AppLocalizations { String get enable_connect => 'कनेक्ट सक्षम करें'; @override - String get enable_connect_description => 'अन्य उपकरणों से Spotube को नियंत्रित करें'; + String get enable_connect_description => + 'अन्य उपकरणों से Spotube को नियंत्रित करें'; @override String get devices => 'उपकरण'; @@ -1076,7 +1105,8 @@ class AppLocalizationsHi extends AppLocalizations { String get choose_the_device => 'उपकरण चुनें:'; @override - String get multiple_device_connected => 'कई उपकरण जुड़े हुए हैं।\nउस उपकरण को चुनें जिस पर आप यह क्रिया करना चाहते हैं'; + String get multiple_device_connected => + 'कई उपकरण जुड़े हुए हैं।\nउस उपकरण को चुनें जिस पर आप यह क्रिया करना चाहते हैं'; @override String get nothing_found => 'कुछ भी नहीं मिला'; @@ -1149,7 +1179,8 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Spotify की प्रति स्ट्रीम भुगतान के आधार पर\n\$0.003 से \$0.005 तक गणना की गई है। यह एक काल्पनिक\nगणना है जो उपयोगकर्ता को यह जानकारी देती है कि वे कितना भुगतान\nकरते यदि वे Spotify पर गाने सुनते।'; + String get streaming_fees_hypothetical => + '*Spotify की प्रति स्ट्रीम भुगतान के आधार पर\n\$0.003 से \$0.005 तक गणना की गई है। यह एक काल्पनिक\nगणना है जो उपयोगकर्ता को यह जानकारी देती है कि वे कितना भुगतान\nकरते यदि वे Spotify पर गाने सुनते।'; @override String get minutes_listened => 'सुनिएका मिनेटहरू'; @@ -1171,7 +1202,8 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1252,8 @@ class AppLocalizationsHi extends AppLocalizations { String get webview_not_found => 'वेबव्यू नहीं मिला'; @override - String get webview_not_found_description => 'आपके डिवाइस पर वेबव्यू रनटाइम इंस्टॉल नहीं है।\nअगर इंस्टॉल है, तो सुनिश्चित करें कि यह environment PATH में है\n\nइंस्टॉल करने के बाद, ऐप को पुनः शुरू करें'; + String get webview_not_found_description => + 'आपके डिवाइस पर वेबव्यू रनटाइम इंस्टॉल नहीं है।\nअगर इंस्टॉल है, तो सुनिश्चित करें कि यह environment PATH में है\n\nइंस्टॉल करने के बाद, ऐप को पुनः शुरू करें'; @override String get unsupported_platform => 'असमर्थित प्लेटफार्म'; @@ -1252,7 +1285,8 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get export_cache_confirmation => 'क्या आप इन फ़ाइलों को निर्यात करना चाहते हैं'; + String get export_cache_confirmation => + 'क्या आप इन फ़ाइलों को निर्यात करना चाहते हैं'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,7 +1315,8 @@ class AppLocalizationsHi extends AppLocalizations { String get view_all => 'सभी देखें'; @override - String get no_tracks_added_yet => 'लगता है आपने अभी तक कोई ट्रैक नहीं जोड़ा है।'; + String get no_tracks_added_yet => + 'लगता है आपने अभी तक कोई ट्रैक नहीं जोड़ा है।'; @override String get no_tracks => 'लगता है यहाँ कोई ट्रैक नहीं है।'; @@ -1290,10 +1325,12 @@ class AppLocalizationsHi extends AppLocalizations { String get no_tracks_listened_yet => 'लगता है आपने अभी तक कुछ नहीं सुना है।'; @override - String get not_following_artists => 'आप किसी भी कलाकार को फॉलो नहीं कर रहे हैं।'; + String get not_following_artists => + 'आप किसी भी कलाकार को फॉलो नहीं कर रहे हैं।'; @override - String get no_favorite_albums_yet => 'लगता है आपने अभी तक कोई एल्बम अपनी पसंदीदा सूची में नहीं जोड़ा है।'; + String get no_favorite_albums_yet => + 'लगता है आपने अभी तक कोई एल्बम अपनी पसंदीदा सूची में नहीं जोड़ा है।'; @override String get no_logs_found => 'कोई लॉग नहीं मिला'; @@ -1317,7 +1354,8 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/यूनिक्स जैसे OS में, .zshrc/.bashrc/.bash_profile आदि में पथ सेट करना काम नहीं करेगा।\nआपको पथ को शेल कॉन्फ़िगरेशन फ़ाइल में सेट करना होगा।'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/यूनिक्स जैसे OS में, .zshrc/.bashrc/.bash_profile आदि में पथ सेट करना काम नहीं करेगा।\nआपको पथ को शेल कॉन्फ़िगरेशन फ़ाइल में सेट करना होगा।'; @override String get download => 'डाउनलोड करें'; @@ -1335,7 +1373,8 @@ class AppLocalizationsHi extends AppLocalizations { String get edit_port => 'पोर्ट संपादित करें'; @override - String get port_helper_msg => 'डिफ़ॉल्ट -1 है जो यादृच्छिक संख्या को दर्शाता है। यदि आपने फ़ायरवॉल कॉन्फ़िगर किया है, तो इसे सेट करना अनुशंसित है।'; + String get port_helper_msg => + 'डिफ़ॉल्ट -1 है जो यादृच्छिक संख्या को दर्शाता है। यदि आपने फ़ायरवॉल कॉन्फ़िगर किया है, तो इसे सेट करना अनुशंसित है।'; @override String connect_request(Object client) { @@ -1343,5 +1382,6 @@ class AppLocalizationsHi extends AppLocalizations { } @override - String get connection_request_denied => 'कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।'; + String get connection_request_denied => + 'कनेक्शन अस्वीकृत। उपयोगकर्ता ने पहुंच अस्वीकृत कर दी।'; } diff --git a/lib/l10n/generated/app_localizations_id.dart b/lib/l10n/generated/app_localizations_id.dart index 851af46d..2ec73009 100644 --- a/lib/l10n/generated/app_localizations_id.dart +++ b/lib/l10n/generated/app_localizations_id.dart @@ -356,7 +356,8 @@ class AppLocalizationsId extends AppLocalizations { String get clear_all => 'Bersihkan semua'; @override - String get show_hide_ui_on_hover => 'Tampil/Sembunyikan UI saat mengarahkan kursor'; + String get show_hide_ui_on_hover => + 'Tampil/Sembunyikan UI saat mengarahkan kursor'; @override String get always_on_top => 'Selalu di atas'; @@ -407,7 +408,8 @@ class AppLocalizationsId extends AppLocalizations { String get layout_mode => 'Mode Tata Letak'; @override - String get override_layout_settings => 'Ganti pengaturan mode tata letak responsif'; + String get override_layout_settings => + 'Ganti pengaturan mode tata letak responsif'; @override String get adaptive => 'Adaptif'; @@ -437,7 +439,8 @@ class AppLocalizationsId extends AppLocalizations { String get sync_album_color => 'Sinkronkan warna album'; @override - String get sync_album_color_description => 'Menggunakan warna dominan sampul album sebagai warna aksen'; + String get sync_album_color_description => + 'Menggunakan warna dominan sampul album sebagai warna aksen'; @override String get playback => 'Pemutaran'; @@ -455,7 +458,8 @@ class AppLocalizationsId extends AppLocalizations { String get pre_download_play => 'Unduh dan putar'; @override - String get pre_download_play_description => 'Daripada streaming audio, unduh byte dan mainkan (Direkomendasikan untuk pengguna bandwidth yang lebih tinggi)'; + String get pre_download_play_description => + 'Daripada streaming audio, unduh byte dan mainkan (Direkomendasikan untuk pengguna bandwidth yang lebih tinggi)'; @override String get skip_non_music => 'Lewati segmen non-musik (SponsorBlock)'; @@ -464,7 +468,8 @@ class AppLocalizationsId extends AppLocalizations { String get blacklist_description => 'Lagu dan artis di daftar hitam'; @override - String get wait_for_download_to_finish => 'Tunggu hingga unduhan saat ini selesai'; + String get wait_for_download_to_finish => + 'Tunggu hingga unduhan saat ini selesai'; @override String get desktop => 'Desktop'; @@ -500,7 +505,8 @@ class AppLocalizationsId extends AppLocalizations { String get please_sponsor => 'Silakan Sponsor/Menyumbang'; @override - String get spotube_description => 'Spotube, klien Spotify yang ringan, lintas platform, dan gratis untuk semua'; + String get spotube_description => + 'Spotube, klien Spotify yang ringan, lintas platform, dan gratis untuk semua'; @override String get version => 'Versi'; @@ -532,7 +538,8 @@ class AppLocalizationsId extends AppLocalizations { String get license => 'Lisensi'; @override - String get credentials_will_not_be_shared_disclaimer => 'Jangan khawatir, kredensial Anda tidak akan dikumpulkan atau dibagikan kepada siapa pun'; + String get credentials_will_not_be_shared_disclaimer => + 'Jangan khawatir, kredensial Anda tidak akan dikumpulkan atau dibagikan kepada siapa pun'; @override String get know_how_to_login => 'Tidak tahu bagaimana melakukan ini?'; @@ -576,19 +583,23 @@ class AppLocalizationsId extends AppLocalizations { String get piped_instance => 'Piped Server Instance'; @override - String get piped_description => 'The Piped server instance untuk digunakan sebagai pencocokan trek'; + String get piped_description => + 'The Piped server instance untuk digunakan sebagai pencocokan trek'; @override - String get piped_warning => 'Beberapa di antaranya mungkin tidak berfungsi dengan baik. Jadi gunakan dengan risiko Anda sendiri'; + String get piped_warning => + 'Beberapa di antaranya mungkin tidak berfungsi dengan baik. Jadi gunakan dengan risiko Anda sendiri'; @override String get invidious_instance => 'Invidious Server Instance'; @override - String get invidious_description => 'The Invidious server instance to use for track matching'; + String get invidious_description => + 'The Invidious server instance to use for track matching'; @override - String get invidious_warning => 'Some of them might not work well. So use at your own risk'; + String get invidious_warning => + 'Some of them might not work well. So use at your own risk'; @override String get generate => 'Generate'; @@ -602,10 +613,12 @@ class AppLocalizationsId extends AppLocalizations { String get replace_downloaded_tracks => 'Ganti semua trek yang diunduh'; @override - String get skip_download_tracks => 'Lewati pengunduhan semua trek yang diunduh'; + String get skip_download_tracks => + 'Lewati pengunduhan semua trek yang diunduh'; @override - String get do_you_want_to_replace => 'Apakah Anda ingin mengganti track yang ada?'; + String get do_you_want_to_replace => + 'Apakah Anda ingin mengganti track yang ada?'; @override String get replace => 'Ganti'; @@ -711,22 +724,28 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get download_warning => 'Jika Anda mengunduh semua Lagu secara massal, Anda jelas membajak Musik & menyebabkan kerusakan pada masyarakat kreatif Musik. Saya harap Anda menyadari hal ini. Selalu berusaha menghormati & mendukung kerja keras Artis'; + String get download_warning => + 'Jika Anda mengunduh semua Lagu secara massal, Anda jelas membajak Musik & menyebabkan kerusakan pada masyarakat kreatif Musik. Saya harap Anda menyadari hal ini. Selalu berusaha menghormati & mendukung kerja keras Artis'; @override - String get download_ip_ban_warning => 'BTW, IP Anda bisa diblokir di YouTube karena permintaan unduhan yang berlebihan dari biasanya. Blokir IP berarti Anda tidak dapat menggunakan YouTube (meskipun Anda masuk) setidaknya selama 2-3 bulan dari perangkat IP tersebut. Dan Spotube tidak bertanggung jawab jika hal ini terjadi'; + String get download_ip_ban_warning => + 'BTW, IP Anda bisa diblokir di YouTube karena permintaan unduhan yang berlebihan dari biasanya. Blokir IP berarti Anda tidak dapat menggunakan YouTube (meskipun Anda masuk) setidaknya selama 2-3 bulan dari perangkat IP tersebut. Dan Spotube tidak bertanggung jawab jika hal ini terjadi'; @override - String get by_clicking_accept_terms => 'Dengan mengklik \'terima\' Anda menyetujui ketentuan berikut:'; + String get by_clicking_accept_terms => + 'Dengan mengklik \'terima\' Anda menyetujui ketentuan berikut:'; @override - String get download_agreement_1 => 'Saya tahu saya membajak Musik. Saya buruk'; + String get download_agreement_1 => + 'Saya tahu saya membajak Musik. Saya buruk'; @override - String get download_agreement_2 => 'Saya akan mendukung Artis di mana pun saya bisa dan saya melakukan ini hanya karena saya tidak punya uang untuk membeli karya seni mereka'; + String get download_agreement_2 => + 'Saya akan mendukung Artis di mana pun saya bisa dan saya melakukan ini hanya karena saya tidak punya uang untuk membeli karya seni mereka'; @override - String get download_agreement_3 => 'Saya sepenuhnya menyadari bahwa IP saya dapat diblokir di YouTube & saya tidak menganggap Spotube atau pemilik/kontributornya bertanggung jawab atas kecelakaan apa pun yang disebabkan oleh tindakan saya saat ini'; + String get download_agreement_3 => + 'Saya sepenuhnya menyadari bahwa IP saya dapat diblokir di YouTube & saya tidak menganggap Spotube atau pemilik/kontributornya bertanggung jawab atas kecelakaan apa pun yang disebabkan oleh tindakan saya saat ini'; @override String get decline => 'Menolak'; @@ -807,7 +826,8 @@ class AppLocalizationsId extends AppLocalizations { String get failed_to_encrypt => 'Gagal mengenkripsi'; @override - String get encryption_failed_warning => 'Spotube menggunakan enkripsi untuk menyimpan data Anda dengan aman. Namun gagal melakukannya. Jadi itu akan kembali ke penyimpanan yang tidak aman\nJika Anda menggunakan linux, pastikan Anda telah menginstal layanan rahasia (gnome-keyring, kde-wallet, keepassxc, dll)'; + String get encryption_failed_warning => + 'Spotube menggunakan enkripsi untuk menyimpan data Anda dengan aman. Namun gagal melakukannya. Jadi itu akan kembali ke penyimpanan yang tidak aman\nJika Anda menggunakan linux, pastikan Anda telah menginstal layanan rahasia (gnome-keyring, kde-wallet, keepassxc, dll)'; @override String get querying_info => 'Mencari informasi...'; @@ -905,7 +925,8 @@ class AppLocalizationsId extends AppLocalizations { String get friends => 'Daftar Teman'; @override - String get no_lyrics_available => 'Maaf, tidak dapat menemukan lirik untuk lagu ini'; + String get no_lyrics_available => + 'Maaf, tidak dapat menemukan lirik untuk lagu ini'; @override String get start_a_radio => 'Putar Radio'; @@ -914,7 +935,8 @@ class AppLocalizationsId extends AppLocalizations { String get how_to_start_radio => 'Bagaimana Anda ingin memutar radio?'; @override - String get replace_queue_question => 'Apakah Anda ingin mengganti antrean saat ini atau menambahkannya?'; + String get replace_queue_question => + 'Apakah Anda ingin mengganti antrean saat ini atau menambahkannya?'; @override String get endless_playback => 'Pemutaran Tanpa Akhir'; @@ -923,7 +945,8 @@ class AppLocalizationsId extends AppLocalizations { String get delete_playlist => 'Hapus Daftar Putar'; @override - String get delete_playlist_confirmation => 'Anda yakin ingin menghapus daftar putar ini?'; + String get delete_playlist_confirmation => + 'Anda yakin ingin menghapus daftar putar ini?'; @override String get local_tracks => 'Trek Lokal'; @@ -941,22 +964,27 @@ class AppLocalizationsId extends AppLocalizations { String get freedom_of_music => '“Kebebasan Musik”'; @override - String get freedom_of_music_palm => '“Kebebasan Musik di telapak tangan Anda”'; + String get freedom_of_music_palm => + '“Kebebasan Musik di telapak tangan Anda”'; @override String get get_started => 'Mari kita mulai'; @override - String get youtube_source_description => 'Direkomendasikan dan berfungsi paling baik.'; + String get youtube_source_description => + 'Direkomendasikan dan berfungsi paling baik.'; @override - String get piped_source_description => 'Merasa bebas? Sama seperti YouTube tetapi banyak yang gratis.'; + String get piped_source_description => + 'Merasa bebas? Sama seperti YouTube tetapi banyak yang gratis.'; @override - String get jiosaavn_source_description => 'Terbaik untuk wilayah Asia Selatan.'; + String get jiosaavn_source_description => + 'Terbaik untuk wilayah Asia Selatan.'; @override - String get invidious_source_description => 'Similar to Piped but with higher availability.'; + String get invidious_source_description => + 'Similar to Piped but with higher availability.'; @override String highest_quality(Object quality) { @@ -967,13 +995,15 @@ class AppLocalizationsId extends AppLocalizations { String get select_audio_source => 'Pilih Sumber Suara'; @override - String get endless_playback_description => 'Tambahkan lagu baru secara otomatis\nke akhir antrean'; + String get endless_playback_description => + 'Tambahkan lagu baru secara otomatis\nke akhir antrean'; @override String get choose_your_region => 'Pilih wilayah Anda'; @override - String get choose_your_region_description => 'Ini akan membantu Spotube menampilkan konten yang tepat\nuntuk lokasi Anda.'; + String get choose_your_region_description => + 'Ini akan membantu Spotube menampilkan konten yang tepat\nuntuk lokasi Anda.'; @override String get choose_your_language => 'Pilih bahasa Anda'; @@ -982,7 +1012,8 @@ class AppLocalizationsId extends AppLocalizations { String get help_project_grow => 'Bantu proyek ini berkembang'; @override - String get help_project_grow_description => 'Spotube adalah proyek sumber terbuka. Anda dapat membantu proyek ini berkembang dengan berkontribusi pada proyek, melaporkan bug, atau menyarankan fitur baru.'; + String get help_project_grow_description => + 'Spotube adalah proyek sumber terbuka. Anda dapat membantu proyek ini berkembang dengan berkontribusi pada proyek, melaporkan bug, atau menyarankan fitur baru.'; @override String get contribute_on_github => 'Berkontribusi di GitHub'; @@ -997,7 +1028,8 @@ class AppLocalizationsId extends AppLocalizations { String get enable_connect => 'Aktifkan Hubungkan'; @override - String get enable_connect_description => 'Kontrol Spotube dari perangkat lain'; + String get enable_connect_description => + 'Kontrol Spotube dari perangkat lain'; @override String get devices => 'Perangkat'; @@ -1076,7 +1108,8 @@ class AppLocalizationsId extends AppLocalizations { String get choose_the_device => 'Pilih perangkat:'; @override - String get multiple_device_connected => 'Beberapa perangkat terhubung.\nPilih perangkat tempat Anda ingin melakukan tindakan ini'; + String get multiple_device_connected => + 'Beberapa perangkat terhubung.\nPilih perangkat tempat Anda ingin melakukan tindakan ini'; @override String get nothing_found => 'Tidak ditemukan apa pun'; @@ -1171,7 +1204,8 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1254,8 @@ class AppLocalizationsId extends AppLocalizations { String get webview_not_found => 'Webview tidak ditemukan'; @override - String get webview_not_found_description => 'Tidak ada runtime Webview yang diinstal di perangkat Anda.\nJika sudah diinstal, pastikan itu ada di environment PATH\n\nSetelah diinstal, restart aplikasi'; + String get webview_not_found_description => + 'Tidak ada runtime Webview yang diinstal di perangkat Anda.\nJika sudah diinstal, pastikan itu ada di environment PATH\n\nSetelah diinstal, restart aplikasi'; @override String get unsupported_platform => 'Platform tidak didukung'; @@ -1252,7 +1287,8 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get export_cache_confirmation => 'Do you want to export these files to'; + String get export_cache_confirmation => + 'Do you want to export these files to'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1317,22 @@ class AppLocalizationsId extends AppLocalizations { String get view_all => 'View all'; @override - String get no_tracks_added_yet => 'Looks like you haven\'t added any tracks yet'; + String get no_tracks_added_yet => + 'Looks like you haven\'t added any tracks yet'; @override String get no_tracks => 'Looks like there are no tracks here'; @override - String get no_tracks_listened_yet => 'Looks like you haven\'t listened to anything yet'; + String get no_tracks_listened_yet => + 'Looks like you haven\'t listened to anything yet'; @override String get not_following_artists => 'You\'re not following any artists'; @override - String get no_favorite_albums_yet => 'Looks like you haven\'t added any albums to your favorites yet'; + String get no_favorite_albums_yet => + 'Looks like you haven\'t added any albums to your favorites yet'; @override String get no_logs_found => 'No logs found'; @@ -1317,7 +1356,8 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'In macOS/Linux/unix like OS\'s, setting path on .zshrc/.bashrc/.bash_profile etc. won\'t work.\nYou need to set the path in the shell configuration file'; + String get youtube_engine_unix_issue_message => + 'In macOS/Linux/unix like OS\'s, setting path on .zshrc/.bashrc/.bash_profile etc. won\'t work.\nYou need to set the path in the shell configuration file'; @override String get download => 'Download'; @@ -1335,7 +1375,8 @@ class AppLocalizationsId extends AppLocalizations { String get edit_port => 'Edit port'; @override - String get port_helper_msg => 'Default adalah -1 yang menunjukkan angka acak. Jika Anda telah mengonfigurasi firewall, disarankan untuk mengatur ini.'; + String get port_helper_msg => + 'Default adalah -1 yang menunjukkan angka acak. Jika Anda telah mengonfigurasi firewall, disarankan untuk mengatur ini.'; @override String connect_request(Object client) { @@ -1343,5 +1384,6 @@ class AppLocalizationsId extends AppLocalizations { } @override - String get connection_request_denied => 'Koneksi ditolak. Pengguna menolak akses.'; + String get connection_request_denied => + 'Koneksi ditolak. Pengguna menolak akses.'; } diff --git a/lib/l10n/generated/app_localizations_it.dart b/lib/l10n/generated/app_localizations_it.dart index 52f8fa98..8039552f 100644 --- a/lib/l10n/generated/app_localizations_it.dart +++ b/lib/l10n/generated/app_localizations_it.dart @@ -407,7 +407,8 @@ class AppLocalizationsIt extends AppLocalizations { String get layout_mode => 'Modalità Layout'; @override - String get override_layout_settings => 'Sovrascrivi le impostazioni del layout responsivo'; + String get override_layout_settings => + 'Sovrascrivi le impostazioni del layout responsivo'; @override String get adaptive => 'Adattiva'; @@ -437,7 +438,8 @@ class AppLocalizationsIt extends AppLocalizations { String get sync_album_color => 'Syncronizza colore album'; @override - String get sync_album_color_description => 'Usa il colore dominante della copertina dell\'album come colore accento'; + String get sync_album_color_description => + 'Usa il colore dominante della copertina dell\'album come colore accento'; @override String get playback => 'Riproduzione'; @@ -455,7 +457,8 @@ class AppLocalizationsIt extends AppLocalizations { String get pre_download_play => 'Pre-scarica e riproduci'; @override - String get pre_download_play_description => 'Anzi che effettuare lo stream dell\'audio, scarica invece i byte e li riproduce (raccomandato per gli utenti con banda più alta)'; + String get pre_download_play_description => + 'Anzi che effettuare lo stream dell\'audio, scarica invece i byte e li riproduce (raccomandato per gli utenti con banda più alta)'; @override String get skip_non_music => 'Salta i segmenti non di musica (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsIt extends AppLocalizations { String get blacklist_description => 'Tracce e artisti in blacklist'; @override - String get wait_for_download_to_finish => 'Prego attendere che lo scaricamento corrente finisca'; + String get wait_for_download_to_finish => + 'Prego attendere che lo scaricamento corrente finisca'; @override String get desktop => 'Desktop'; @@ -500,7 +504,8 @@ class AppLocalizationsIt extends AppLocalizations { String get please_sponsor => 'Per favore sponsorizza/dona'; @override - String get spotube_description => 'Spotube, un client spotify gratis per tutti, multipiattaforma e leggero'; + String get spotube_description => + 'Spotube, un client spotify gratis per tutti, multipiattaforma e leggero'; @override String get version => 'Versione'; @@ -532,7 +537,8 @@ class AppLocalizationsIt extends AppLocalizations { String get license => 'Licenza'; @override - String get credentials_will_not_be_shared_disclaimer => 'Non ti preoccupare, le tue credenziali non saranno inviate o condivise con nessuno'; + String get credentials_will_not_be_shared_disclaimer => + 'Non ti preoccupare, le tue credenziali non saranno inviate o condivise con nessuno'; @override String get know_how_to_login => 'Non sai come farlo?'; @@ -576,19 +582,23 @@ class AppLocalizationsIt extends AppLocalizations { String get piped_instance => 'Istanza Server Piped'; @override - String get piped_description => 'L\'istanza server Piped da usare per il match della tracccia'; + String get piped_description => + 'L\'istanza server Piped da usare per il match della tracccia'; @override - String get piped_warning => 'Alcune di queste non funzioneranno benen. Usa quindi a tuo rischio'; + String get piped_warning => + 'Alcune di queste non funzioneranno benen. Usa quindi a tuo rischio'; @override String get invidious_instance => 'Istanza del server Invidious'; @override - String get invidious_description => 'L\'istanza del server Invidious da utilizzare per il matching delle tracce'; + String get invidious_description => + 'L\'istanza del server Invidious da utilizzare per il matching delle tracce'; @override - String get invidious_warning => 'Alcuni potrebbero non funzionare bene. Usali a tuo rischio'; + String get invidious_warning => + 'Alcuni potrebbero non funzionare bene. Usali a tuo rischio'; @override String get generate => 'Genera'; @@ -599,13 +609,16 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Sostituisci tutte le tracce scaricate'; + String get replace_downloaded_tracks => + 'Sostituisci tutte le tracce scaricate'; @override - String get skip_download_tracks => 'Salta lo scaricamento di tutte le tracce scaricate'; + String get skip_download_tracks => + 'Salta lo scaricamento di tutte le tracce scaricate'; @override - String get do_you_want_to_replace => 'Vuoi sovrascrivere la traccia esistente??'; + String get do_you_want_to_replace => + 'Vuoi sovrascrivere la traccia esistente??'; @override String get replace => 'Sovrascrivi'; @@ -711,22 +724,28 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get download_warning => 'Se scarichi tutte le Tracce in massa stai chiaramente piratando Musica e causando un danno alla società creativa della Musica. Spero che tu sia cosciente di questo. Cerca di rispettare e supportare sempre il duro lavoro degli Artisti'; + String get download_warning => + 'Se scarichi tutte le Tracce in massa stai chiaramente piratando Musica e causando un danno alla società creativa della Musica. Spero che tu sia cosciente di questo. Cerca di rispettare e supportare sempre il duro lavoro degli Artisti'; @override - String get download_ip_ban_warning => 'A proposito, il tuo IP può essere bloccato da YouTube per il numero di richieste di download eccessive rispetto la norma. Il blocco IP significa che non puoi usare YoutTube (anche hai effettuato l\'accesso) per almeno 2-3 mesi dal dispositivo con questo IP. Spotube non ha responsabilità se questo dovesse accadere'; + String get download_ip_ban_warning => + 'A proposito, il tuo IP può essere bloccato da YouTube per il numero di richieste di download eccessive rispetto la norma. Il blocco IP significa che non puoi usare YoutTube (anche hai effettuato l\'accesso) per almeno 2-3 mesi dal dispositivo con questo IP. Spotube non ha responsabilità se questo dovesse accadere'; @override - String get by_clicking_accept_terms => 'Cliccando su \'accetta\' concordi con i seguenti termini:'; + String get by_clicking_accept_terms => + 'Cliccando su \'accetta\' concordi con i seguenti termini:'; @override - String get download_agreement_1 => 'So che sto piratando Musica. Sono cattivo'; + String get download_agreement_1 => + 'So che sto piratando Musica. Sono cattivo'; @override - String get download_agreement_2 => 'Supporterò l\'Artista come potrò e sto facendo questo solo perchè non ho denaro per acquistare il suo prodotto dell\'ingegno'; + String get download_agreement_2 => + 'Supporterò l\'Artista come potrò e sto facendo questo solo perchè non ho denaro per acquistare il suo prodotto dell\'ingegno'; @override - String get download_agreement_3 => 'Sono completamente cosciente che il mio IP può essere bloccato da YouTube & non riterrò responsabili Spotube o i suoi autori/contributori per ogni inconveniente causato dalla mia azione corrente'; + String get download_agreement_3 => + 'Sono completamente cosciente che il mio IP può essere bloccato da YouTube & non riterrò responsabili Spotube o i suoi autori/contributori per ogni inconveniente causato dalla mia azione corrente'; @override String get decline => 'Declino'; @@ -807,7 +826,8 @@ class AppLocalizationsIt extends AppLocalizations { String get failed_to_encrypt => 'Criptazione fallita'; @override - String get encryption_failed_warning => 'Spotube usa la criptazione per memorizzare in modo sicuro i dati. Ma ha fallito a farlo. Passerà quindi in ripiego alla memorizzazione non siscura\nSe stai usando Linux assicurati di avere un servizio di segretezza installato (gnome-keyring, kde-wallet, keepassxc etc)'; + String get encryption_failed_warning => + 'Spotube usa la criptazione per memorizzare in modo sicuro i dati. Ma ha fallito a farlo. Passerà quindi in ripiego alla memorizzazione non siscura\nSe stai usando Linux assicurati di avere un servizio di segretezza installato (gnome-keyring, kde-wallet, keepassxc etc)'; @override String get querying_info => 'Richiesta informazioni...'; @@ -905,7 +925,8 @@ class AppLocalizationsIt extends AppLocalizations { String get friends => 'Amici'; @override - String get no_lyrics_available => 'Spiacente, impossibile trovare il testo di questa traccia'; + String get no_lyrics_available => + 'Spiacente, impossibile trovare il testo di questa traccia'; @override String get start_a_radio => 'Avvia una Radio'; @@ -914,7 +935,8 @@ class AppLocalizationsIt extends AppLocalizations { String get how_to_start_radio => 'Come vuoi avviare la radio?'; @override - String get replace_queue_question => 'Vuoi sostituire la coda attuale o aggiungerla?'; + String get replace_queue_question => + 'Vuoi sostituire la coda attuale o aggiungerla?'; @override String get endless_playback => 'Riproduzione Infinita'; @@ -923,7 +945,8 @@ class AppLocalizationsIt extends AppLocalizations { String get delete_playlist => 'Elimina Playlist'; @override - String get delete_playlist_confirmation => 'Sei sicuro di voler eliminare questa playlist?'; + String get delete_playlist_confirmation => + 'Sei sicuro di voler eliminare questa playlist?'; @override String get local_tracks => 'Tracce Locali'; @@ -941,7 +964,8 @@ class AppLocalizationsIt extends AppLocalizations { String get freedom_of_music => '“Libertà della Musica”'; @override - String get freedom_of_music_palm => '“Libertà della Musica nel palmo della tua mano”'; + String get freedom_of_music_palm => + '“Libertà della Musica nel palmo della tua mano”'; @override String get get_started => 'Cominciamo'; @@ -950,13 +974,16 @@ class AppLocalizationsIt extends AppLocalizations { String get youtube_source_description => 'Consigliato e funziona meglio.'; @override - String get piped_source_description => 'Ti senti libero? Come YouTube ma molto più gratuito.'; + String get piped_source_description => + 'Ti senti libero? Come YouTube ma molto più gratuito.'; @override - String get jiosaavn_source_description => 'Il migliore per la regione dell\'Asia meridionale.'; + String get jiosaavn_source_description => + 'Il migliore per la regione dell\'Asia meridionale.'; @override - String get invidious_source_description => 'Simile a Piped ma con maggiore disponibilità.'; + String get invidious_source_description => + 'Simile a Piped ma con maggiore disponibilità.'; @override String highest_quality(Object quality) { @@ -967,13 +994,15 @@ class AppLocalizationsIt extends AppLocalizations { String get select_audio_source => 'Seleziona Sorgente Audio'; @override - String get endless_playback_description => 'Aggiungi automaticamente nuove canzoni alla fine della coda'; + String get endless_playback_description => + 'Aggiungi automaticamente nuove canzoni alla fine della coda'; @override String get choose_your_region => 'Scegli la tua regione'; @override - String get choose_your_region_description => 'Questo aiuterà Spotube a mostrarti il contenuto giusto per la tua posizione.'; + String get choose_your_region_description => + 'Questo aiuterà Spotube a mostrarti il contenuto giusto per la tua posizione.'; @override String get choose_your_language => 'Scegli la tua lingua'; @@ -982,7 +1011,8 @@ class AppLocalizationsIt extends AppLocalizations { String get help_project_grow => 'Aiuta questo progetto a crescere'; @override - String get help_project_grow_description => 'Spotube è un progetto open-source. Puoi aiutare questo progetto a crescere contribuendo al progetto, segnalando bug o suggerendo nuove funzionalità.'; + String get help_project_grow_description => + 'Spotube è un progetto open-source. Puoi aiutare questo progetto a crescere contribuendo al progetto, segnalando bug o suggerendo nuove funzionalità.'; @override String get contribute_on_github => 'Contribuisci su GitHub'; @@ -997,7 +1027,8 @@ class AppLocalizationsIt extends AppLocalizations { String get enable_connect => 'Abilita connessione'; @override - String get enable_connect_description => 'Controlla Spotube da altri dispositivi'; + String get enable_connect_description => + 'Controlla Spotube da altri dispositivi'; @override String get devices => 'Dispositivi'; @@ -1076,7 +1107,8 @@ class AppLocalizationsIt extends AppLocalizations { String get choose_the_device => 'Scegli il dispositivo:'; @override - String get multiple_device_connected => 'Sono collegati più dispositivi.\nScegli il dispositivo su cui vuoi che venga eseguita questa azione'; + String get multiple_device_connected => + 'Sono collegati più dispositivi.\nScegli il dispositivo su cui vuoi che venga eseguita questa azione'; @override String get nothing_found => 'Nessun risultato'; @@ -1171,7 +1203,8 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1253,8 @@ class AppLocalizationsIt extends AppLocalizations { String get webview_not_found => 'Webview non trovato'; @override - String get webview_not_found_description => 'Nessun runtime Webview installato nel tuo dispositivo.\nSe è installato, assicurati che sia nel environment PATH\n\nDopo l\'installazione, riavvia l\'app'; + String get webview_not_found_description => + 'Nessun runtime Webview installato nel tuo dispositivo.\nSe è installato, assicurati che sia nel environment PATH\n\nDopo l\'installazione, riavvia l\'app'; @override String get unsupported_platform => 'Piattaforma non supportata'; @@ -1281,19 +1315,22 @@ class AppLocalizationsIt extends AppLocalizations { String get view_all => 'Vedi tutto'; @override - String get no_tracks_added_yet => 'Sembra che non hai ancora aggiunto nessun brano'; + String get no_tracks_added_yet => + 'Sembra che non hai ancora aggiunto nessun brano'; @override String get no_tracks => 'Sembra che non ci siano brani qui'; @override - String get no_tracks_listened_yet => 'Sembra che non hai ascoltato nulla ancora'; + String get no_tracks_listened_yet => + 'Sembra che non hai ascoltato nulla ancora'; @override String get not_following_artists => 'Non stai seguendo alcun artista'; @override - String get no_favorite_albums_yet => 'Sembra che non hai ancora aggiunto album ai tuoi preferiti'; + String get no_favorite_albums_yet => + 'Sembra che non hai ancora aggiunto album ai tuoi preferiti'; @override String get no_logs_found => 'Nessun registro trovato'; @@ -1317,7 +1354,8 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'In macOS/Linux/os simili a unix, impostare il percorso su .zshrc/.bashrc/.bash_profile ecc. non funzionerà.\nDevi impostare il percorso nel file di configurazione della shell'; + String get youtube_engine_unix_issue_message => + 'In macOS/Linux/os simili a unix, impostare il percorso su .zshrc/.bashrc/.bash_profile ecc. non funzionerà.\nDevi impostare il percorso nel file di configurazione della shell'; @override String get download => 'Scarica'; @@ -1335,7 +1373,8 @@ class AppLocalizationsIt extends AppLocalizations { String get edit_port => 'Modifica porta'; @override - String get port_helper_msg => 'Il valore predefinito è -1, che indica un numero casuale. Se hai configurato un firewall, si consiglia di impostarlo.'; + String get port_helper_msg => + 'Il valore predefinito è -1, che indica un numero casuale. Se hai configurato un firewall, si consiglia di impostarlo.'; @override String connect_request(Object client) { @@ -1343,5 +1382,6 @@ class AppLocalizationsIt extends AppLocalizations { } @override - String get connection_request_denied => 'Connessione negata. L\'utente ha negato l\'accesso.'; + String get connection_request_denied => + 'Connessione negata. L\'utente ha negato l\'accesso.'; } diff --git a/lib/l10n/generated/app_localizations_ja.dart b/lib/l10n/generated/app_localizations_ja.dart index a8491c68..a4b6795d 100644 --- a/lib/l10n/generated/app_localizations_ja.dart +++ b/lib/l10n/generated/app_localizations_ja.dart @@ -455,7 +455,8 @@ class AppLocalizationsJa extends AppLocalizations { String get pre_download_play => '事前ダウンロードと再生'; @override - String get pre_download_play_description => '音声をストリーミングする代わりに、データをバイト単位でダウンロードして再生 (回線速度が早いユーザーにおすすめ)'; + String get pre_download_play_description => + '音声をストリーミングする代わりに、データをバイト単位でダウンロードして再生 (回線速度が早いユーザーにおすすめ)'; @override String get skip_non_music => '音楽でない部分をスキップ (SponsorBlock)'; @@ -500,7 +501,8 @@ class AppLocalizationsJa extends AppLocalizations { String get please_sponsor => '出資/寄付もお待ちします'; @override - String get spotube_description => 'Spotube は、軽量でクロスプラットフォームな、すべて無料の spotify クライアント'; + String get spotube_description => + 'Spotube は、軽量でクロスプラットフォームな、すべて無料の spotify クライアント'; @override String get version => 'バージョン'; @@ -532,7 +534,8 @@ class AppLocalizationsJa extends AppLocalizations { String get license => 'ライセンス'; @override - String get credentials_will_not_be_shared_disclaimer => '心配ありません。個人情報を収集したり、共有されることはありません'; + String get credentials_will_not_be_shared_disclaimer => + '心配ありません。個人情報を収集したり、共有されることはありません'; @override String get know_how_to_login => 'やり方が分からないですか?'; @@ -711,10 +714,12 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get download_warning => '全曲の一括ダウンロードは明らかに音楽への海賊行為であり、音楽を生み出す共同体に損害を与えるでしょう。気づいてほしい。アーティストの多大な努力に敬意を払い、支援するようにしてください'; + String get download_warning => + '全曲の一括ダウンロードは明らかに音楽への海賊行為であり、音楽を生み出す共同体に損害を与えるでしょう。気づいてほしい。アーティストの多大な努力に敬意を払い、支援するようにしてください'; @override - String get download_ip_ban_warning => 'また、通常よりも過剰なダウンロード要求があれば、YouTubeはあなたのIPをブロックします。つまりそのIPの端末からは、少なくとも2-3か月の間、(ログインしても)YouTubeを利用できなくなりす。そうなっても Spotube は一切の責任を負いません'; + String get download_ip_ban_warning => + 'また、通常よりも過剰なダウンロード要求があれば、YouTubeはあなたのIPをブロックします。つまりそのIPの端末からは、少なくとも2-3か月の間、(ログインしても)YouTubeを利用できなくなりす。そうなっても Spotube は一切の責任を負いません'; @override String get by_clicking_accept_terms => '「同意する」のクリックにより、以下への同意となります:'; @@ -726,7 +731,8 @@ class AppLocalizationsJa extends AppLocalizations { String get download_agreement_2 => '芸術作品を買うお金がないのでそうするしかないが、アーティストをできる限り支援する'; @override - String get download_agreement_3 => '私のIPがYouTubeにブロックされることがあると完全に把握した。私のこの行動により起きたどんな事故も、Spotube やその所有者/貢献者に責任はありません。'; + String get download_agreement_3 => + '私のIPがYouTubeにブロックされることがあると完全に把握した。私のこの行動により起きたどんな事故も、Spotube やその所有者/貢献者に責任はありません。'; @override String get decline => '同意しない'; @@ -807,7 +813,8 @@ class AppLocalizationsJa extends AppLocalizations { String get failed_to_encrypt => '暗号化に失敗しました'; @override - String get encryption_failed_warning => 'Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください'; + String get encryption_failed_warning => + 'Spotubeはデータを安全に保存するために暗号化を使用しています。しかし、失敗しました。したがって、安全でないストレージにフォールバックします\nLinuxを使用している場合は、gnome-keyring、kde-wallet、keepassxcなどのシークレットサービスがインストールされていることを確認してください'; @override String get querying_info => '情報を取得中...'; @@ -973,7 +980,8 @@ class AppLocalizationsJa extends AppLocalizations { String get choose_your_region => '地域を選択'; @override - String get choose_your_region_description => 'これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。'; + String get choose_your_region_description => + 'これにより、Spotubeがあなたの場所に適したコンテンツを表示できます。'; @override String get choose_your_language => '言語を選択してください'; @@ -982,7 +990,8 @@ class AppLocalizationsJa extends AppLocalizations { String get help_project_grow => 'このプロジェクトの成長を支援する'; @override - String get help_project_grow_description => 'Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。'; + String get help_project_grow_description => + 'Spotubeはオープンソースプロジェクトです。プロジェクトに貢献したり、バグを報告したり、新しい機能を提案することで、このプロジェクトの成長に貢献できます。'; @override String get contribute_on_github => 'GitHubで貢献する'; @@ -1076,7 +1085,8 @@ class AppLocalizationsJa extends AppLocalizations { String get choose_the_device => 'デバイスを選択:'; @override - String get multiple_device_connected => '複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください'; + String get multiple_device_connected => + '複数のデバイスが接続されています。\nこのアクションを実行するデバイスを選択してください'; @override String get nothing_found => '何も見つかりませんでした'; @@ -1149,7 +1159,8 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*これは Spotify のストリームあたりの支払い\nが \$0.003 から \$0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。'; + String get streaming_fees_hypothetical => + '*これは Spotify のストリームあたりの支払い\nが \$0.003 から \$0.005 であると仮定して計算されています。\nこれは、Spotify でその曲を聴いた場合にアーティストにいくら支払ったかの\n洞察を得るための仮定の計算です。'; @override String get minutes_listened => 'リスニング時間'; @@ -1171,7 +1182,8 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1232,8 @@ class AppLocalizationsJa extends AppLocalizations { String get webview_not_found => 'Webviewが見つかりません'; @override - String get webview_not_found_description => 'デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください'; + String get webview_not_found_description => + 'デバイスにWebviewランタイムがインストールされていません。\nインストールされている場合は、environment PATHにあることを確認してください\n\nインストール後、アプリを再起動してください'; @override String get unsupported_platform => 'サポートされていないプラットフォーム'; @@ -1317,7 +1330,8 @@ class AppLocalizationsJa extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/Unix系OSでは、.zshrc/.bashrc/.bash_profileなどでパスを設定しても動作しません。\nシェルの設定ファイルにパスを設定する必要があります'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/Unix系OSでは、.zshrc/.bashrc/.bash_profileなどでパスを設定しても動作しません。\nシェルの設定ファイルにパスを設定する必要があります'; @override String get download => 'ダウンロード'; @@ -1335,7 +1349,8 @@ class AppLocalizationsJa extends AppLocalizations { String get edit_port => 'ポートを編集'; @override - String get port_helper_msg => 'デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。'; + String get port_helper_msg => + 'デフォルトは-1で、ランダムな番号を示します。ファイアウォールを設定している場合は、これを設定することをお勧めします。'; @override String connect_request(Object client) { diff --git a/lib/l10n/generated/app_localizations_ka.dart b/lib/l10n/generated/app_localizations_ka.dart index 36954dc5..1dbbeb20 100644 --- a/lib/l10n/generated/app_localizations_ka.dart +++ b/lib/l10n/generated/app_localizations_ka.dart @@ -407,7 +407,8 @@ class AppLocalizationsKa extends AppLocalizations { String get layout_mode => 'განლაგების რეჟიმი'; @override - String get override_layout_settings => 'რესფონსივ განლაგების რეჟიმის კონფიგურაციაზე გადაწერა'; + String get override_layout_settings => + 'რესფონსივ განლაგების რეჟიმის კონფიგურაციაზე გადაწერა'; @override String get adaptive => 'ადაპტირებული'; @@ -437,7 +438,8 @@ class AppLocalizationsKa extends AppLocalizations { String get sync_album_color => 'ალბომის ფერის სინქრონიზაცია'; @override - String get sync_album_color_description => 'დომინანტური ალბომის ფერის აქცენტის ფერად გამოყენება'; + String get sync_album_color_description => + 'დომინანტური ალბომის ფერის აქცენტის ფერად გამოყენება'; @override String get playback => 'დაკვრა'; @@ -455,16 +457,19 @@ class AppLocalizationsKa extends AppLocalizations { String get pre_download_play => 'წინასწარ ჩამოტვირთვა და დაკვრა'; @override - String get pre_download_play_description => 'აუდიოს სტრიმინგის ნაცვლად, ბაიტების ჩამოტვირთვა და დაკვრა (რეკომენდებულია უფრო მაღალი გამტარუნარიანობის მომხმარებლებისთვის)'; + String get pre_download_play_description => + 'აუდიოს სტრიმინგის ნაცვლად, ბაიტების ჩამოტვირთვა და დაკვრა (რეკომენდებულია უფრო მაღალი გამტარუნარიანობის მომხმარებლებისთვის)'; @override - String get skip_non_music => 'არა მუსიკალური ნაწილის გამოტოვება (სპონსორის ბლოკი)'; + String get skip_non_music => + 'არა მუსიკალური ნაწილის გამოტოვება (სპონსორის ბლოკი)'; @override String get blacklist_description => 'შავ სიაში მყოფი არტისტები და ტრეკები'; @override - String get wait_for_download_to_finish => 'გთხოვთ, დაელოდოთ მიმდინარე ჩამოტვირთვის დასრულებას'; + String get wait_for_download_to_finish => + 'გთხოვთ, დაელოდოთ მიმდინარე ჩამოტვირთვის დასრულებას'; @override String get desktop => 'დესკტოპი'; @@ -500,7 +505,8 @@ class AppLocalizationsKa extends AppLocalizations { String get please_sponsor => 'გთხოვთ დაგვასპონსოროთ'; @override - String get spotube_description => 'Spotube, a lightweight, cross-platform, free-for-all spotify client'; + String get spotube_description => + 'Spotube, a lightweight, cross-platform, free-for-all spotify client'; @override String get version => 'ვერსია'; @@ -532,13 +538,15 @@ class AppLocalizationsKa extends AppLocalizations { String get license => 'ლიცენზია'; @override - String get credentials_will_not_be_shared_disclaimer => 'არ ინერვიულოთ, თქვენი მონაცემები არ იქნება შეგროვებული ან გაზიარებული ვინმესთან'; + String get credentials_will_not_be_shared_disclaimer => + 'არ ინერვიულოთ, თქვენი მონაცემები არ იქნება შეგროვებული ან გაზიარებული ვინმესთან'; @override String get know_how_to_login => 'არ იცით როგორ გააკეთოთ ეს?'; @override - String get follow_step_by_step_guide => 'მიჰყევით ნაბიჯ-ნაბიჯ სახელმძღვანელოს'; + String get follow_step_by_step_guide => + 'მიჰყევით ნაბიჯ-ნაბიჯ სახელმძღვანელოს'; @override String cookie_name_cookie(Object name) { @@ -576,7 +584,8 @@ class AppLocalizationsKa extends AppLocalizations { String get piped_instance => 'Piped Server Instance'; @override - String get piped_description => 'The Piped server instance to use for track matching'; + String get piped_description => + 'The Piped server instance to use for track matching'; @override String get piped_warning => 'ზოგიერთი მათგანმა შეიძლება კარგად არ იმუშაოს. '; @@ -585,10 +594,12 @@ class AppLocalizationsKa extends AppLocalizations { String get invidious_instance => 'Invidious სერვერის ინსტანცია'; @override - String get invidious_description => 'Invidious სერვერის ინსტანცია, რომელიც გამოიყენება ტრეკის შესატყვისად'; + String get invidious_description => + 'Invidious სერვერის ინსტანცია, რომელიც გამოიყენება ტრეკის შესატყვისად'; @override - String get invidious_warning => 'ზოგიერთი შეიძლება კარგად არ მუშაობდეს. გამოიყენეთ თქვენს პასუხისმგებლობაზე'; + String get invidious_warning => + 'ზოგიერთი შეიძლება კარგად არ მუშაობდეს. გამოიყენეთ თქვენს პასუხისმგებლობაზე'; @override String get generate => 'გააგენერირეთ'; @@ -703,7 +714,8 @@ class AppLocalizationsKa extends AppLocalizations { String get are_you_sure => 'Დარწმუნებული ხართ?'; @override - String get generating_playlist => 'მიმდინარეობს თქვენი მორგებული ფლეილისტის გენერირება...'; + String get generating_playlist => + 'მიმდინარეობს თქვენი მორგებული ფლეილისტის გენერირება...'; @override String selected_count_tracks(Object count) { @@ -711,22 +723,27 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get download_warning => 'If you download all Tracks at bulk you\'re clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist\'s hard work'; + String get download_warning => + 'If you download all Tracks at bulk you\'re clearly pirating Music & causing damage to the creative society of Music. I hope you are aware of this. Always, try respecting & supporting Artist\'s hard work'; @override - String get download_ip_ban_warning => 'BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can\'t use YouTube (even if you\'re logged in) for at least 2-3 months from that IP device. And Spotube doesn\'t hold any responsibility if this ever happens'; + String get download_ip_ban_warning => + 'BTW, your IP can get blocked on YouTube due excessive download requests than usual. IP block means you can\'t use YouTube (even if you\'re logged in) for at least 2-3 months from that IP device. And Spotube doesn\'t hold any responsibility if this ever happens'; @override - String get by_clicking_accept_terms => 'By clicking \'accept\' you agree to following terms:'; + String get by_clicking_accept_terms => + 'By clicking \'accept\' you agree to following terms:'; @override String get download_agreement_1 => 'I know I\'m pirating Music. I\'m bad'; @override - String get download_agreement_2 => 'I\'ll support the Artist wherever I can and I\'m only doing this because I don\'t have money to buy their art'; + String get download_agreement_2 => + 'I\'ll support the Artist wherever I can and I\'m only doing this because I don\'t have money to buy their art'; @override - String get download_agreement_3 => 'I\'m completely aware that my IP can get blocked on YouTube & I don\'t hold Spotube or his owners/contributors responsible for any accidents caused by my current action'; + String get download_agreement_3 => + 'I\'m completely aware that my IP can get blocked on YouTube & I don\'t hold Spotube or his owners/contributors responsible for any accidents caused by my current action'; @override String get decline => 'უარყოფა'; @@ -807,7 +824,8 @@ class AppLocalizationsKa extends AppLocalizations { String get failed_to_encrypt => 'დაშიფვრა ვერ მოხერხდა'; @override - String get encryption_failed_warning => 'Spotube uses encryption to securely store your data. But failed to do so. So it\'ll fallback to insecure storage\nIf you\'re using linux, please make sure you\'ve any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed'; + String get encryption_failed_warning => + 'Spotube uses encryption to securely store your data. But failed to do so. So it\'ll fallback to insecure storage\nIf you\'re using linux, please make sure you\'ve any secret-service (gnome-keyring, kde-wallet, keepassxc etc) installed'; @override String get querying_info => 'Querying info...'; @@ -905,7 +923,8 @@ class AppLocalizationsKa extends AppLocalizations { String get friends => 'მეგობრები'; @override - String get no_lyrics_available => 'უკაცრავად, ამ ტრეკისთვის ტექსტის პოვნა შეუძლებელია'; + String get no_lyrics_available => + 'უკაცრავად, ამ ტრეკისთვის ტექსტის პოვნა შეუძლებელია'; @override String get start_a_radio => 'რადიოს ჩართვა'; @@ -914,7 +933,8 @@ class AppLocalizationsKa extends AppLocalizations { String get how_to_start_radio => 'როგორ გნებავთ რადიოს ჩართვა?'; @override - String get replace_queue_question => 'გნებავთ ჩაანაცვლოთ არსებული რიგი თუ დაამატოთ მასზე?'; + String get replace_queue_question => + 'გნებავთ ჩაანაცვლოთ არსებული რიგი თუ დაამატოთ მასზე?'; @override String get endless_playback => 'დაუსრულებელი დაკვრა'; @@ -923,7 +943,8 @@ class AppLocalizationsKa extends AppLocalizations { String get delete_playlist => 'ფლეილისტის წაშლა'; @override - String get delete_playlist_confirmation => 'დარწმუნებული ხართ რომ გნებავთ ფლეილისტის წაშლა?'; + String get delete_playlist_confirmation => + 'დარწმუნებული ხართ რომ გნებავთ ფლეილისტის წაშლა?'; @override String get local_tracks => 'ლოკალური ტრეკები'; @@ -947,16 +968,20 @@ class AppLocalizationsKa extends AppLocalizations { String get get_started => 'დავიწყოთ'; @override - String get youtube_source_description => 'რეკომენდებულია და მუშაობს საუკეთესოდ.'; + String get youtube_source_description => + 'რეკომენდებულია და მუშაობს საუკეთესოდ.'; @override - String get piped_source_description => 'თავისუფლად გრძნობთ თავს? იგივეა, რაც YouTube, მაგრამ ბევრი თავისუფალი.'; + String get piped_source_description => + 'თავისუფლად გრძნობთ თავს? იგივეა, რაც YouTube, მაგრამ ბევრი თავისუფალი.'; @override - String get jiosaavn_source_description => 'საუკეთესოა სამხრეთ აზიის რეგიონისთვის.'; + String get jiosaavn_source_description => + 'საუკეთესოა სამხრეთ აზიის რეგიონისთვის.'; @override - String get invidious_source_description => 'მსგავსია Piped-ის, მაგრამ მაღალი ხელმისაწვდომობით.'; + String get invidious_source_description => + 'მსგავსია Piped-ის, მაგრამ მაღალი ხელმისაწვდომობით.'; @override String highest_quality(Object quality) { @@ -967,13 +992,15 @@ class AppLocalizationsKa extends AppLocalizations { String get select_audio_source => 'აუდიოს წყაროს არჩევა'; @override - String get endless_playback_description => 'ახალი სიმთერების ავტომატურად რიგის ბოლოში დამატება'; + String get endless_playback_description => + 'ახალი სიმთერების ავტომატურად რიგის ბოლოში დამატება'; @override String get choose_your_region => 'აირჩიე შენი რეგიონი'; @override - String get choose_your_region_description => 'This will help Spotube show you the right content\nfor your location.'; + String get choose_your_region_description => + 'This will help Spotube show you the right content\nfor your location.'; @override String get choose_your_language => 'აირჩიე ენა'; @@ -982,7 +1009,8 @@ class AppLocalizationsKa extends AppLocalizations { String get help_project_grow => 'დაეხმარეთ ამ პროექტს განვითარებაში'; @override - String get help_project_grow_description => 'Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.'; + String get help_project_grow_description => + 'Spotube is an open-source project. You can help this project grow by contributing to the project, reporting bugs, or suggesting new features.'; @override String get contribute_on_github => 'GitHub-ზე კონტრიბუცია'; @@ -997,7 +1025,8 @@ class AppLocalizationsKa extends AppLocalizations { String get enable_connect => 'დაკავშირების ჩართვა'; @override - String get enable_connect_description => 'აკონტროლე Spotube სხვა მოწყობილობებიდან'; + String get enable_connect_description => + 'აკონტროლე Spotube სხვა მოწყობილობებიდან'; @override String get devices => 'მოწყობილობები'; @@ -1076,7 +1105,8 @@ class AppLocalizationsKa extends AppLocalizations { String get choose_the_device => 'აირჩიეთ მოწყობილობა:'; @override - String get multiple_device_connected => 'დაკავშირებულია რამდენიმე მოწყობილობა.\nაირჩიეთ მოწყობილობა, რომელზეც უნდა განხორციელდეს ეს მოქმედება'; + String get multiple_device_connected => + 'დაკავშირებულია რამდენიმე მოწყობილობა.\nაირჩიეთ მოწყობილობა, რომელზეც უნდა განხორციელდეს ეს მოქმედება'; @override String get nothing_found => 'არაფერი მოიძებნა'; @@ -1149,7 +1179,8 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*ეს рассчитывается на основе выплат за поток от Spotify\nот \$0.003 до \$0.005. ეს ჰიპოთეტური გამოთვლა იძლევა მომხმარებელს წარმოდგენას იმაზე, რამდენად\nგადახდილი იქნებოდა არტისტებისთვის, თუ მათ მოუსმინოს Spotify-ს ტრეკებს.'; + String get streaming_fees_hypothetical => + '*ეს рассчитывается на основе выплат за поток от Spotify\nот \$0.003 до \$0.005. ეს ჰიპოთეტური გამოთვლა იძლევა მომხმარებელს წარმოდგენას იმაზე, რამდენად\nგადახდილი იქნებოდა არტისტებისთვის, თუ მათ მოუსმინოს Spotify-ს ტრეკებს.'; @override String get minutes_listened => 'წუთები მოუსმინეს'; @@ -1171,7 +1202,8 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1252,8 @@ class AppLocalizationsKa extends AppLocalizations { String get webview_not_found => 'ვებვიუ ვერ მოიძებნა'; @override - String get webview_not_found_description => 'თქვენს მოწყობილობაზე ვებვიუის შესრულების დრო არ არის დაყენებული.\nთუ დაყენებულია, დარწმუნდით, რომ ის environment PATH-შია\n\nდაყენების შემდეგ, გადატვირთეთ აპი'; + String get webview_not_found_description => + 'თქვენს მოწყობილობაზე ვებვიუის შესრულების დრო არ არის დაყენებული.\nთუ დაყენებულია, დარწმუნდით, რომ ის environment PATH-შია\n\nდაყენების შემდეგ, გადატვირთეთ აპი'; @override String get unsupported_platform => 'მოუხერხებელი პლატფორმა'; @@ -1281,19 +1314,22 @@ class AppLocalizationsKa extends AppLocalizations { String get view_all => 'ყველა ნახვა'; @override - String get no_tracks_added_yet => 'გაჩნდება რომ ჯერ არ გაქვთ დამატებული ტრეკები'; + String get no_tracks_added_yet => + 'გაჩნდება რომ ჯერ არ გაქვთ დამატებული ტრეკები'; @override String get no_tracks => 'გავლებული არ ჩანს არ არსებობს ტრეკები'; @override - String get no_tracks_listened_yet => 'გქონდეთ გრძნობა, რომ ჯერ არაფერი უსმენია'; + String get no_tracks_listened_yet => + 'გქონდეთ გრძნობა, რომ ჯერ არაფერი უსმენია'; @override String get not_following_artists => 'არ მიჰყვებით რომელიმე არტისტს'; @override - String get no_favorite_albums_yet => 'გაჩნდება რომ ჯერ არ გაქვთ დამატებული ალბომები თქვენს ფავორიტებში'; + String get no_favorite_albums_yet => + 'გაჩნდება რომ ჯერ არ გაქვთ დამატებული ალბომები თქვენს ფავორიტებში'; @override String get no_logs_found => 'ჩაწერები ვერ მოიძებნა'; @@ -1317,7 +1353,8 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/Unix მსგავსი ოპერაციული სისტემებში, .zshrc/.bashrc/.bash_profile-ით პათის დაყენება ვერ იმუშავებს.\nთქვენ უნდა დააყენოთ პათი შელ ფაილში'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/Unix მსგავსი ოპერაციული სისტემებში, .zshrc/.bashrc/.bash_profile-ით პათის დაყენება ვერ იმუშავებს.\nთქვენ უნდა დააყენოთ პათი შელ ფაილში'; @override String get download => 'ჩამოტვირთვა'; @@ -1335,7 +1372,8 @@ class AppLocalizationsKa extends AppLocalizations { String get edit_port => 'პორტის რედაქტირება'; @override - String get port_helper_msg => 'ნაგულისხმევი არის -1, რაც შემთხვევითი ნომრის მითითებას ნიშნავს. თუ لديك firewall настроен, рекомендуется установить это.'; + String get port_helper_msg => + 'ნაგულისხმევი არის -1, რაც შემთხვევითი ნომრის მითითებას ნიშნავს. თუ لديك firewall настроен, рекомендуется установить это.'; @override String connect_request(Object client) { @@ -1343,5 +1381,6 @@ class AppLocalizationsKa extends AppLocalizations { } @override - String get connection_request_denied => 'კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა.'; + String get connection_request_denied => + 'კავშირი უარყოფილია. მომხმარებელმა უარყო წვდომა.'; } diff --git a/lib/l10n/generated/app_localizations_ko.dart b/lib/l10n/generated/app_localizations_ko.dart index e7fa9abd..dbe0c06f 100644 --- a/lib/l10n/generated/app_localizations_ko.dart +++ b/lib/l10n/generated/app_localizations_ko.dart @@ -455,7 +455,8 @@ class AppLocalizationsKo extends AppLocalizations { String get pre_download_play => '재생할 곡을 미리 다운로드'; @override - String get pre_download_play_description => '스트리밍 방식을 쓰는 대신 파일 단위로 다운로드 받고 재생 (인터넷 대역폭이 높은 환경에서 추천)'; + String get pre_download_play_description => + '스트리밍 방식을 쓰는 대신 파일 단위로 다운로드 받고 재생 (인터넷 대역폭이 높은 환경에서 추천)'; @override String get skip_non_music => '음악이 아닌 부분을 스킵 (SponsorBlock)'; @@ -500,7 +501,8 @@ class AppLocalizationsKo extends AppLocalizations { String get please_sponsor => '후원해주시면 감사하겠습니다.'; @override - String get spotube_description => 'Spotube는, 경량에 크로스플랫폼인데다 무료이기까지한 스포티파이 클라이언트입니다'; + String get spotube_description => + 'Spotube는, 경량에 크로스플랫폼인데다 무료이기까지한 스포티파이 클라이언트입니다'; @override String get version => '버전'; @@ -532,7 +534,8 @@ class AppLocalizationsKo extends AppLocalizations { String get license => '라이선스'; @override - String get credentials_will_not_be_shared_disclaimer => '걱정마세요. 개인정보를 수집하거나 공유하지 않습니다.'; + String get credentials_will_not_be_shared_disclaimer => + '걱정마세요. 개인정보를 수집하거나 공유하지 않습니다.'; @override String get know_how_to_login => '어떻게 하는건지 모르겠나요?'; @@ -711,10 +714,12 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get download_warning => '모든 트랙을 대량으로 다운로드하는 것은 명백한 불법 복제이며 음악 창작 사회에 피해를 입히는 행위입니다. 이 점을 알아주셨으면 합니다. 항상 아티스트의 노력을 존중하고 응원해 주세요.'; + String get download_warning => + '모든 트랙을 대량으로 다운로드하는 것은 명백한 불법 복제이며 음악 창작 사회에 피해를 입히는 행위입니다. 이 점을 알아주셨으면 합니다. 항상 아티스트의 노력을 존중하고 응원해 주세요.'; @override - String get download_ip_ban_warning => '참고로, 평소보다 과도한 다운로드 요청으로 인해 YouTube에서 IP가 차단될 수 있습니다. IP 차단은 해당 IP 기기에서 최소 2~3개월 동안 (로그인한 상태에서도) YouTube를 사용할 수 없음을 의미합니다. 그리고 이런 일이 발생하더라도 스포튜브는 어떠한 책임도 지지 않습니다.'; + String get download_ip_ban_warning => + '참고로, 평소보다 과도한 다운로드 요청으로 인해 YouTube에서 IP가 차단될 수 있습니다. IP 차단은 해당 IP 기기에서 최소 2~3개월 동안 (로그인한 상태에서도) YouTube를 사용할 수 없음을 의미합니다. 그리고 이런 일이 발생하더라도 스포튜브는 어떠한 책임도 지지 않습니다.'; @override String get by_clicking_accept_terms => '\'동의\'를 클릭하면 다음 약관에 동의하는 것입니다:'; @@ -723,10 +728,12 @@ class AppLocalizationsKo extends AppLocalizations { String get download_agreement_1 => '알고 있습니다. 전 나쁜 사람입니다.'; @override - String get download_agreement_2 => '제가 할 수 있는 모든 곳에서 아티스트를 지원할 것이며, 저는 그들의 작품을 살 돈이 없기 때문에 이렇게 하는 것뿐입니다.'; + String get download_agreement_2 => + '제가 할 수 있는 모든 곳에서 아티스트를 지원할 것이며, 저는 그들의 작품을 살 돈이 없기 때문에 이렇게 하는 것뿐입니다.'; @override - String get download_agreement_3 => '본인은 YouTube에서 내 IP가 차단될 수 있음을 완전히 알고 있으며, 현재 내 행동으로 인해 발생하는 사고에 대해 Spotube 또는 그 소유자/기여자에게 책임을 묻지 않습니다.'; + String get download_agreement_3 => + '본인은 YouTube에서 내 IP가 차단될 수 있음을 완전히 알고 있으며, 현재 내 행동으로 인해 발생하는 사고에 대해 Spotube 또는 그 소유자/기여자에게 책임을 묻지 않습니다.'; @override String get decline => '거절'; @@ -807,7 +814,8 @@ class AppLocalizationsKo extends AppLocalizations { String get failed_to_encrypt => '암호화에 실패했습니다'; @override - String get encryption_failed_warning => 'Spotube는 암호화를 사용하여 데이터를 안전하게 저장합니다. 하지만 그렇게 하지 못했습니다. 따라서 안전하지 않은 저장소로 대체됩니다.\n리눅스를 사용하는 경우, 비밀 서비스(gnome-keyring, kde-wallet, keepassxc 등)가 설치되어 있는지 확인하세요.'; + String get encryption_failed_warning => + 'Spotube는 암호화를 사용하여 데이터를 안전하게 저장합니다. 하지만 그렇게 하지 못했습니다. 따라서 안전하지 않은 저장소로 대체됩니다.\n리눅스를 사용하는 경우, 비밀 서비스(gnome-keyring, kde-wallet, keepassxc 등)가 설치되어 있는지 확인하세요.'; @override String get querying_info => '정보를 얻는 중...'; @@ -950,7 +958,8 @@ class AppLocalizationsKo extends AppLocalizations { String get youtube_source_description => '추천되며 가장 잘 작동합니다.'; @override - String get piped_source_description => '자유로운 기분이 듭니까? YouTube와 같지만 훨씬 더 무료합니다.'; + String get piped_source_description => + '자유로운 기분이 듭니까? YouTube와 같지만 훨씬 더 무료합니다.'; @override String get jiosaavn_source_description => '남아시아 지역에 최적입니다.'; @@ -973,7 +982,8 @@ class AppLocalizationsKo extends AppLocalizations { String get choose_your_region => '지역 선택'; @override - String get choose_your_region_description => '이것은 Spotube가 위치에 맞는 콘텐츠를 표시하는 데 도움이 됩니다.'; + String get choose_your_region_description => + '이것은 Spotube가 위치에 맞는 콘텐츠를 표시하는 데 도움이 됩니다.'; @override String get choose_your_language => '언어 선택'; @@ -982,7 +992,8 @@ class AppLocalizationsKo extends AppLocalizations { String get help_project_grow => '이 프로젝트 성장에 도움을 주세요'; @override - String get help_project_grow_description => 'Spotube는 오픈 소스 프로젝트입니다. 프로젝트에 기여하거나 버그를 보고하거나 새로운 기능을 제안하여이 프로젝트의 성장에 도움을 줄 수 있습니다.'; + String get help_project_grow_description => + 'Spotube는 오픈 소스 프로젝트입니다. 프로젝트에 기여하거나 버그를 보고하거나 새로운 기능을 제안하여이 프로젝트의 성장에 도움을 줄 수 있습니다.'; @override String get contribute_on_github => 'GitHub에서 기여하기'; @@ -1076,7 +1087,8 @@ class AppLocalizationsKo extends AppLocalizations { String get choose_the_device => '디바이스 선택:'; @override - String get multiple_device_connected => '여러 디바이스가 연결되어 있습니다.\n이 작업을 실행할 디바이스를 선택하세요'; + String get multiple_device_connected => + '여러 디바이스가 연결되어 있습니다.\n이 작업을 실행할 디바이스를 선택하세요'; @override String get nothing_found => '찾을 수 없음'; @@ -1149,7 +1161,8 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*이것은 Spotify의 스트림당 지급액\n\$0.003에서 \$0.005를 기준으로 계산된 것입니다.\n이것은 사용자가 Spotify에서 곡을 들었을 때\n아티스트에게 지불했을 금액에 대한 통찰을 제공하기 위한\n가상의 계산입니다.'; + String get streaming_fees_hypothetical => + '*이것은 Spotify의 스트림당 지급액\n\$0.003에서 \$0.005를 기준으로 계산된 것입니다.\n이것은 사용자가 Spotify에서 곡을 들었을 때\n아티스트에게 지불했을 금액에 대한 통찰을 제공하기 위한\n가상의 계산입니다.'; @override String get minutes_listened => '청취한 시간'; @@ -1171,7 +1184,8 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1234,8 @@ class AppLocalizationsKo extends AppLocalizations { String get webview_not_found => '웹뷰를 찾을 수 없음'; @override - String get webview_not_found_description => '기기에 웹뷰 런타임이 설치되지 않았습니다.\n설치되어 있으면 environment PATH에 있는지 확인하십시오\n\n설치 후 앱을 다시 시작하세요'; + String get webview_not_found_description => + '기기에 웹뷰 런타임이 설치되지 않았습니다.\n설치되어 있으면 environment PATH에 있는지 확인하십시오\n\n설치 후 앱을 다시 시작하세요'; @override String get unsupported_platform => '지원되지 않는 플랫폼'; @@ -1317,7 +1332,8 @@ class AppLocalizationsKo extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/unix와 같은 운영 체제에서는 .zshrc/.bashrc/.bash_profile 등에 경로 설정이 작동하지 않습니다.\n셸 구성 파일에 경로를 설정해야 합니다'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/unix와 같은 운영 체제에서는 .zshrc/.bashrc/.bash_profile 등에 경로 설정이 작동하지 않습니다.\n셸 구성 파일에 경로를 설정해야 합니다'; @override String get download => '다운로드'; @@ -1335,7 +1351,8 @@ class AppLocalizationsKo extends AppLocalizations { String get edit_port => '포트 편집'; @override - String get port_helper_msg => '기본값은 -1로 무작위 숫자를 나타냅니다. 방화벽이 구성된 경우 이를 설정하는 것이 좋습니다.'; + String get port_helper_msg => + '기본값은 -1로 무작위 숫자를 나타냅니다. 방화벽이 구성된 경우 이를 설정하는 것이 좋습니다.'; @override String connect_request(Object client) { diff --git a/lib/l10n/generated/app_localizations_ne.dart b/lib/l10n/generated/app_localizations_ne.dart index bad7b05e..f0aa95ad 100644 --- a/lib/l10n/generated/app_localizations_ne.dart +++ b/lib/l10n/generated/app_localizations_ne.dart @@ -309,7 +309,8 @@ class AppLocalizationsNe extends AppLocalizations { String get mini_player => 'मिनि प्लेयर'; @override - String get slide_to_seek => 'अगाडि वा पछाडि खोजी गर्नका लागि स्लाइड गर्नुहोस्'; + String get slide_to_seek => + 'अगाडि वा पछाडि खोजी गर्नका लागि स्लाइड गर्नुहोस्'; @override String get shuffle_playlist => 'प्लेलिस्ट शफल गर्नुहोस्'; @@ -407,7 +408,8 @@ class AppLocalizationsNe extends AppLocalizations { String get layout_mode => 'लेआउट मोड'; @override - String get override_layout_settings => 'अनुकूलित प्रतिकृयात्मक लेआउट मोड सेटिङ्गहरू'; + String get override_layout_settings => + 'अनुकूलित प्रतिकृयात्मक लेआउट मोड सेटिङ्गहरू'; @override String get adaptive => 'अनुकूलित'; @@ -437,7 +439,8 @@ class AppLocalizationsNe extends AppLocalizations { String get sync_album_color => 'एल्बम रङ्ग सिङ्क गर्नुहोस्'; @override - String get sync_album_color_description => 'एल्बम कला को प्रमुख रङ्गलाई एक्सेन्ट रङ्गको रूपमा प्रयोग गर्दछ'; + String get sync_album_color_description => + 'एल्बम कला को प्रमुख रङ्गलाई एक्सेन्ट रङ्गको रूपमा प्रयोग गर्दछ'; @override String get playback => 'प्लेब्याक'; @@ -455,16 +458,19 @@ class AppLocalizationsNe extends AppLocalizations { String get pre_download_play => 'पूर्व-डाउनलोड र प्ले गर्नुहोस्'; @override - String get pre_download_play_description => 'आडियो स्ट्रिम गर्नु नगरी बाइटहरू डाउनलोड गरी बजाउँछ (उच्च ब्यान्डविथ उपयोगकर्ताहरूको लागि सिफारिस गरिएको)'; + String get pre_download_play_description => + 'आडियो स्ट्रिम गर्नु नगरी बाइटहरू डाउनलोड गरी बजाउँछ (उच्च ब्यान्डविथ उपयोगकर्ताहरूको लागि सिफारिस गरिएको)'; @override - String get skip_non_music => 'गीतहरू बाहेक कुनै अनुष्ठान छोड्नुहोस् (स्पन्सरब्लक)'; + String get skip_non_music => + 'गीतहरू बाहेक कुनै अनुष्ठान छोड्नुहोस् (स्पन्सरब्लक)'; @override String get blacklist_description => 'कालोसूची गीत र कलाकारहरू'; @override - String get wait_for_download_to_finish => 'कृपया हालको डाउनलोड समाप्त हुन लागि पर्खनुहोस्'; + String get wait_for_download_to_finish => + 'कृपया हालको डाउनलोड समाप्त हुन लागि पर्खनुहोस्'; @override String get desktop => 'डेस्कटप'; @@ -485,7 +491,8 @@ class AppLocalizationsNe extends AppLocalizations { String get about => 'बारेमा'; @override - String get u_love_spotube => 'हामीले थाहा पारेका छौं तपाईंलाई Spotube मन पर्छ'; + String get u_love_spotube => + 'हामीले थाहा पारेका छौं तपाईंलाई Spotube मन पर्छ'; @override String get check_for_updates => 'अपडेटहरूको लागि जाँच गर्नुहोस्'; @@ -500,7 +507,8 @@ class AppLocalizationsNe extends AppLocalizations { String get please_sponsor => 'कृपया स्पन्सर/डोनेट गर्नुहोस्'; @override - String get spotube_description => 'Spotube, एक हल्का, समृद्ध, स्वतन्त्र Spotify क्लाइयन'; + String get spotube_description => + 'Spotube, एक हल्का, समृद्ध, स्वतन्त्र Spotify क्लाइयन'; @override String get version => 'संस्करण'; @@ -532,13 +540,15 @@ class AppLocalizationsNe extends AppLocalizations { String get license => 'लाइसेन्स'; @override - String get credentials_will_not_be_shared_disclaimer => 'चिन्ता नगर्नुहोस्, तपाईंको कुनै पनि क्रेडेन्शियलहरूले कसैले संग्रह वा साझा गर्नेछैन'; + String get credentials_will_not_be_shared_disclaimer => + 'चिन्ता नगर्नुहोस्, तपाईंको कुनै पनि क्रेडेन्शियलहरूले कसैले संग्रह वा साझा गर्नेछैन'; @override String get know_how_to_login => 'कसरी लगिन गर्ने भन्ने थाहा छैन?'; @override - String get follow_step_by_step_guide => 'चरणबद्ध मार्गदर्शनमा साथी बनाउनुहोस्'; + String get follow_step_by_step_guide => + 'चरणबद्ध मार्गदर्शनमा साथी बनाउनुहोस्'; @override String cookie_name_cookie(Object name) { @@ -576,19 +586,23 @@ class AppLocalizationsNe extends AppLocalizations { String get piped_instance => 'पाइपड सर्भर इन्स्ट्यान्स'; @override - String get piped_description => 'गीत मिलाउको लागि प्रयोग गर्ने पाइपड सर्भर इन्स्ट्यान्स'; + String get piped_description => + 'गीत मिलाउको लागि प्रयोग गर्ने पाइपड सर्भर इन्स्ट्यान्स'; @override - String get piped_warning => 'तिनीहरूमध्ये केहि ठिक गर्न सक्छ। यसलाई आफ्नो जोखिममा प्रयोग गर्नुहोस्'; + String get piped_warning => + 'तिनीहरूमध्ये केहि ठिक गर्न सक्छ। यसलाई आफ्नो जोखिममा प्रयोग गर्नुहोस्'; @override String get invidious_instance => 'Invidious सर्भर इन्स्टेन्स'; @override - String get invidious_description => 'ट्र्याक मिलाउनका लागि प्रयोग हुने Invidious सर्भर इन्स्टेन्स'; + String get invidious_description => + 'ट्र्याक मिलाउनका लागि प्रयोग हुने Invidious सर्भर इन्स्टेन्स'; @override - String get invidious_warning => 'केहीले राम्रोसँग काम नगर्न सक्छ। आफ्नो जोखिममा प्रयोग गर्नुहोस्'; + String get invidious_warning => + 'केहीले राम्रोसँग काम नगर्न सक्छ। आफ्नो जोखिममा प्रयोग गर्नुहोस्'; @override String get generate => 'जनरेट'; @@ -599,13 +613,16 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'सबै डाउनलोड गरिएका ट्र्याकहरूलाई परिवर्तन गर्नुहोस्'; + String get replace_downloaded_tracks => + 'सबै डाउनलोड गरिएका ट्र्याकहरूलाई परिवर्तन गर्नुहोस्'; @override - String get skip_download_tracks => 'सबै डाउनलोड गरिएका ट्र्याकहरूलाई छोड्नुहोस्'; + String get skip_download_tracks => + 'सबै डाउनलोड गरिएका ट्र्याकहरूलाई छोड्नुहोस्'; @override - String get do_you_want_to_replace => 'के तपाईंले वर्तमान ट्र्याकलाई परिवर्तन गर्न चाहनुहुन्छ?'; + String get do_you_want_to_replace => + 'के तपाईंले वर्तमान ट्र्याकलाई परिवर्तन गर्न चाहनुहुन्छ?'; @override String get replace => 'परिवर्तन गर्नुहोस्'; @@ -711,22 +728,28 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get download_warning => 'यदि तपाईं सबै ट्र्याकहरूलाई बल्कमा डाउनलोड गर्छनु हो भने तपाईं स्पष्ट रूपमा साङ्गीत चोरी गरिरहेका छन् र यो साङ्गीतको रचनात्मक समाजलाई क्षति पनि पुर्याउँछ। उमेराइएको छ कि तपाईं यसको बारेमा जागरूक छिनुहुन्छ। सधैं, कला गर्दै र कलाकारको कडा परम्परा समर्थन गर्दै आइन्छ।'; + String get download_warning => + 'यदि तपाईं सबै ट्र्याकहरूलाई बल्कमा डाउनलोड गर्छनु हो भने तपाईं स्पष्ट रूपमा साङ्गीत चोरी गरिरहेका छन् र यो साङ्गीतको रचनात्मक समाजलाई क्षति पनि पुर्याउँछ। उमेराइएको छ कि तपाईं यसको बारेमा जागरूक छिनुहुन्छ। सधैं, कला गर्दै र कलाकारको कडा परम्परा समर्थन गर्दै आइन्छ।'; @override - String get download_ip_ban_warning => 'बितिएका डाउनलोड अनुरोधहरूका कारण तपाईंको आइपीले YouTube मा ब्लक हुन सक्छ। आइपी ब्लक भनेको कम्तीमा 2-3 महिनासम्म तपाईं त्यस आइपी यन्त्रबाट YouTube प्रयोग गर्न सक्नुहुन्छ। र यदि यो हुँदैछ भने स्पट्यूबले यसलाई कसैले गरेको बारेमा कुनै दायित्व लिन्छैन।'; + String get download_ip_ban_warning => + 'बितिएका डाउनलोड अनुरोधहरूका कारण तपाईंको आइपीले YouTube मा ब्लक हुन सक्छ। आइपी ब्लक भनेको कम्तीमा 2-3 महिनासम्म तपाईं त्यस आइपी यन्त्रबाट YouTube प्रयोग गर्न सक्नुहुन्छ। र यदि यो हुँदैछ भने स्पट्यूबले यसलाई कसैले गरेको बारेमा कुनै दायित्व लिन्छैन।'; @override - String get by_clicking_accept_terms => '\'स्वीकृत\' गरेर तपाईं निम्नलिखित निर्वाचन गर्दैछिन्:'; + String get by_clicking_accept_terms => + '\'स्वीकृत\' गरेर तपाईं निम्नलिखित निर्वाचन गर्दैछिन्:'; @override - String get download_agreement_1 => 'म मन्ने छु कि म साङ्गीत चोरी गरिरहेको छु। म बुरो हुँ'; + String get download_agreement_1 => + 'म मन्ने छु कि म साङ्गीत चोरी गरिरहेको छु। म बुरो हुँ'; @override - String get download_agreement_2 => 'म कहिल्यै कहिल्यै तिनीहरूलाई समर्थन गर्नेछु र म यो तिनीहरूको कला किन्ने पैसा छैन भने मा मात्र यो गरेको छु'; + String get download_agreement_2 => + 'म कहिल्यै कहिल्यै तिनीहरूलाई समर्थन गर्नेछु र म यो तिनीहरूको कला किन्ने पैसा छैन भने मा मात्र यो गरेको छु'; @override - String get download_agreement_3 => 'म पूरा रूपमा जान्छु कि मेरो आइपी YouTube मा ब्लक हुन सक्छ र म मन्छेहरूले मेरो चासोबाट भएको कुनै दुर्घटनामा स्पट्यूब वा तिनीहरूको मालिकहरू/सहयोगीहरूलाई दायित्वी ठान्छुँभन्ने पूर्ण जानकारी छैन'; + String get download_agreement_3 => + 'म पूरा रूपमा जान्छु कि मेरो आइपी YouTube मा ब्लक हुन सक्छ र म मन्छेहरूले मेरो चासोबाट भएको कुनै दुर्घटनामा स्पट्यूब वा तिनीहरूको मालिकहरू/सहयोगीहरूलाई दायित्वी ठान्छुँभन्ने पूर्ण जानकारी छैन'; @override String get decline => 'अस्वीकृत'; @@ -807,7 +830,8 @@ class AppLocalizationsNe extends AppLocalizations { String get failed_to_encrypt => 'एन्क्रिप्ट गर्न सकिएन'; @override - String get encryption_failed_warning => 'स्पट्यूबले तपाईंको डेटा सुरक्षित रूपमा स्टोर गर्नका लागि एन्क्रिप्ट गर्न खोजेको छ। तर यसले गरेको छैन। यसले असुरक्षित स्टोरेजमा फल्लब्याक गर्दछ\nयदि तपाईंले लिनक्स प्रयोग गरिरहेका छन् भने कृपया सुनिश्चित गर्नुहोस् कि तपाईंले कुनै सीक्रेट-सर्भिस (गोनोम-किरिङ, केडीइ-वालेट, किपासेक्ससि इत्यादि) इन्स्टल गरेका छौं'; + String get encryption_failed_warning => + 'स्पट्यूबले तपाईंको डेटा सुरक्षित रूपमा स्टोर गर्नका लागि एन्क्रिप्ट गर्न खोजेको छ। तर यसले गरेको छैन। यसले असुरक्षित स्टोरेजमा फल्लब्याक गर्दछ\nयदि तपाईंले लिनक्स प्रयोग गरिरहेका छन् भने कृपया सुनिश्चित गर्नुहोस् कि तपाईंले कुनै सीक्रेट-सर्भिस (गोनोम-किरिङ, केडीइ-वालेट, किपासेक्ससि इत्यादि) इन्स्टल गरेका छौं'; @override String get querying_info => 'जानकारी हेर्दै...'; @@ -824,7 +848,8 @@ class AppLocalizationsNe extends AppLocalizations { String get you_are_offline => 'तपाईं वर्तमान अफलाइन हुनुहुन्छ'; @override - String get connection_restored => 'तपाईंको इन्टरनेट कनेक्सन पुन: स्थापित भएको छ'; + String get connection_restored => + 'तपाईंको इन्टरनेट कनेक्सन पुन: स्थापित भएको छ'; @override String get use_system_title_bar => 'सिस्टम शीर्षक पट्टी प्रयोग गर्नुहोस्'; @@ -833,7 +858,8 @@ class AppLocalizationsNe extends AppLocalizations { String get crunching_results => 'परिणामहरू कपालबाट पीस्दै...'; @override - String get search_to_get_results => 'परिणामहरू प्राप्त गर्नका लागि खोज्नुहोस्'; + String get search_to_get_results => + 'परिणामहरू प्राप्त गर्नका लागि खोज्नुहोस्'; @override String get use_amoled_mode => 'कृष्ण ब्ल्याक गाढा थिम प्रयोग गर्नुहोस्'; @@ -881,7 +907,8 @@ class AppLocalizationsNe extends AppLocalizations { String get login => 'लगइन'; @override - String get login_with_your_lastfm => 'तपाईंको लास्ट.एफ.एम खातामा लगइन गर्नुहोस्'; + String get login_with_your_lastfm => + 'तपाईंको लास्ट.एफ.एम खातामा लगइन गर्नुहोस्'; @override String get scrobble_to_lastfm => 'लास्ट.एफ.एम मा स्क्रबल गर्नुहोस्'; @@ -905,7 +932,8 @@ class AppLocalizationsNe extends AppLocalizations { String get friends => 'साथीहरू'; @override - String get no_lyrics_available => 'क्षमा गर्दैछौं, यस ट्र्याकका लागि गीतका शब्दहरू फेला परेन'; + String get no_lyrics_available => + 'क्षमा गर्दैछौं, यस ट्र्याकका लागि गीतका शब्दहरू फेला परेन'; @override String get start_a_radio => 'रेडियो सुरु गर्नुहोस्'; @@ -914,7 +942,8 @@ class AppLocalizationsNe extends AppLocalizations { String get how_to_start_radio => 'तपाईं रेडियो कसरी सुरु गर्न चाहानुहुन्छ?'; @override - String get replace_queue_question => 'के तपाईं वर्तमान कताक्ष कोट बदल्न चाहानुहुन्छ वा यसलाई थप्नुहुन्छ?'; + String get replace_queue_question => + 'के तपाईं वर्तमान कताक्ष कोट बदल्न चाहानुहुन्छ वा यसलाई थप्नुहुन्छ?'; @override String get endless_playback => 'अनन्त प्लेब्याक'; @@ -923,7 +952,8 @@ class AppLocalizationsNe extends AppLocalizations { String get delete_playlist => 'प्लेलिस्ट मेटाउनुहोस्'; @override - String get delete_playlist_confirmation => 'के तपाईं यो प्लेलिस्ट मेटाउन निश्चित हुनुहुन्छ?'; + String get delete_playlist_confirmation => + 'के तपाईं यो प्लेलिस्ट मेटाउन निश्चित हुनुहुन्छ?'; @override String get local_tracks => 'स्थानिय ट्र्याकहरू'; @@ -950,10 +980,12 @@ class AppLocalizationsNe extends AppLocalizations { String get youtube_source_description => 'सिफारिस गरिएको र बेस्ट काम गर्दछ।'; @override - String get piped_source_description => 'मुक्त सुस्त? YouTube जस्तै तर धेरै मुक्त।'; + String get piped_source_description => + 'मुक्त सुस्त? YouTube जस्तै तर धेरै मुक्त।'; @override - String get jiosaavn_source_description => 'दक्षिण एशियाली क्षेत्रको लागि सर्वोत्तम।'; + String get jiosaavn_source_description => + 'दक्षिण एशियाली क्षेत्रको लागि सर्वोत्तम।'; @override String get invidious_source_description => 'Piped जस्तै तर उच्च उपलब्धतासँग।'; @@ -967,13 +999,15 @@ class AppLocalizationsNe extends AppLocalizations { String get select_audio_source => 'आडियो स्रोत चयन गर्नुहोस्'; @override - String get endless_playback_description => 'नयाँ गीतहरूलाई स्वचालित रूपमा कताक्षको अन्तमा जोड्नुहोस्'; + String get endless_playback_description => + 'नयाँ गीतहरूलाई स्वचालित रूपमा कताक्षको अन्तमा जोड्नुहोस्'; @override String get choose_your_region => 'तपाईंको क्षेत्र छनौट गर्नुहोस्'; @override - String get choose_your_region_description => 'यो Spotubeलाई तपाईंको स्थानका लागि सहि सामग्री देखाउने मद्दत गर्नेछ।'; + String get choose_your_region_description => + 'यो Spotubeलाई तपाईंको स्थानका लागि सहि सामग्री देखाउने मद्दत गर्नेछ।'; @override String get choose_your_language => 'तपाईंको भाषा छनौट गर्नुहोस्'; @@ -982,7 +1016,8 @@ class AppLocalizationsNe extends AppLocalizations { String get help_project_grow => 'यस परियोजनामा वृद्धि गराउनुहोस्'; @override - String get help_project_grow_description => 'Spotube एक खुला स्रोतको परियोजना हो। तपाईं परियोजनामा योगदान गरेर, त्रुटिहरू सूचिकै, वा नयाँ सुविधाहरू सुझाव दिएर यस परियोजनामा वृद्धि गर्न सक्नुहुन्छ।'; + String get help_project_grow_description => + 'Spotube एक खुला स्रोतको परियोजना हो। तपाईं परियोजनामा योगदान गरेर, त्रुटिहरू सूचिकै, वा नयाँ सुविधाहरू सुझाव दिएर यस परियोजनामा वृद्धि गर्न सक्नुहुन्छ।'; @override String get contribute_on_github => 'GitHubमा योगदान गर्नुहोस्'; @@ -997,7 +1032,8 @@ class AppLocalizationsNe extends AppLocalizations { String get enable_connect => 'कनेक्ट सक्रिय गर्नुहोस्'; @override - String get enable_connect_description => 'अन्य उपकरणहरूबाट Spotube कन्ट्रोल गर्नुहोस्'; + String get enable_connect_description => + 'अन्य उपकरणहरूबाट Spotube कन्ट्रोल गर्नुहोस्'; @override String get devices => 'उपकरणहरू'; @@ -1076,7 +1112,8 @@ class AppLocalizationsNe extends AppLocalizations { String get choose_the_device => 'उपकरण चयन गर्नुहोस्:'; @override - String get multiple_device_connected => 'धेरै उपकरण जडान गरिएको छ।\nयो क्रियाकलाप गर्ने उपकरण चयन गर्नुहोस्'; + String get multiple_device_connected => + 'धेरै उपकरण जडान गरिएको छ।\nयो क्रियाकलाप गर्ने उपकरण चयन गर्नुहोस्'; @override String get nothing_found => 'केही फेला परेन'; @@ -1149,7 +1186,8 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*यो Spotify को प्रति स्ट्रिमको आधारमा गणना गरिएको छ\n\$0.003 देखि \$0.005 बीचको भुक्तानी। यो एक काल्पनिक गणना हो\nउपयोगकर्तालाई यो थाहा दिनको लागि कि उनीहरूले अर्टिस्टहरूलाई\nSpotify मा गीत सुनेको भए कति भुक्तानी गर्ने थिए।'; + String get streaming_fees_hypothetical => + '*यो Spotify को प्रति स्ट्रिमको आधारमा गणना गरिएको छ\n\$0.003 देखि \$0.005 बीचको भुक्तानी। यो एक काल्पनिक गणना हो\nउपयोगकर्तालाई यो थाहा दिनको लागि कि उनीहरूले अर्टिस्टहरूलाई\nSpotify मा गीत सुनेको भए कति भुक्तानी गर्ने थिए।'; @override String get minutes_listened => 'सुनिएका मिनेटहरू'; @@ -1171,7 +1209,8 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1259,8 @@ class AppLocalizationsNe extends AppLocalizations { String get webview_not_found => 'वेबभ्यू फेला परेन'; @override - String get webview_not_found_description => 'तपाईंको उपकरणमा कुनै वेबभ्यू रनटाइम स्थापना गरिएको छैन।\nयदि स्थापना गरिएको छ भने, environment PATH मा छ कि छैन भनेर सुनिश्चित गर्नुहोस्\n\nस्थापना पछि, अनुप्रयोग पुनः सुरु गर्नुहोस्'; + String get webview_not_found_description => + 'तपाईंको उपकरणमा कुनै वेबभ्यू रनटाइम स्थापना गरिएको छैन।\nयदि स्थापना गरिएको छ भने, environment PATH मा छ कि छैन भनेर सुनिश्चित गर्नुहोस्\n\nस्थापना पछि, अनुप्रयोग पुनः सुरु गर्नुहोस्'; @override String get unsupported_platform => 'असमर्थित प्लेटफार्म'; @@ -1281,19 +1321,22 @@ class AppLocalizationsNe extends AppLocalizations { String get view_all => 'सभी देखें'; @override - String get no_tracks_added_yet => 'लगता है आपने अभी तक कोई ट्रैक नहीं जोड़ा है'; + String get no_tracks_added_yet => + 'लगता है आपने अभी तक कोई ट्रैक नहीं जोड़ा है'; @override String get no_tracks => 'यहाँ कोई ट्रैक नहीं दिख रहे हैं'; @override - String get no_tracks_listened_yet => 'आपने अभी तक कुछ नहीं सुना है ऐसा लगता है'; + String get no_tracks_listened_yet => + 'आपने अभी तक कुछ नहीं सुना है ऐसा लगता है'; @override String get not_following_artists => 'आप किसी कलाकार को फॉलो नहीं कर रहे हैं'; @override - String get no_favorite_albums_yet => 'लगता है आपने अभी तक कोई एल्बम पसंदीदा में नहीं जोड़ा है'; + String get no_favorite_albums_yet => + 'लगता है आपने अभी तक कोई एल्बम पसंदीदा में नहीं जोड़ा है'; @override String get no_logs_found => 'कोई लॉग नहीं मिला'; @@ -1317,7 +1360,8 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/unix जैसे ऑपरेटिंग सिस्टम में, .zshrc/.bashrc/.bash_profile आदि में पथ सेट करना काम नहीं करेगा।\nआपको शेल कॉन्फ़िगरेशन फ़ाइल में पथ सेट करना होगा'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/unix जैसे ऑपरेटिंग सिस्टम में, .zshrc/.bashrc/.bash_profile आदि में पथ सेट करना काम नहीं करेगा।\nआपको शेल कॉन्फ़िगरेशन फ़ाइल में पथ सेट करना होगा'; @override String get download => 'डाउनलोड'; @@ -1335,7 +1379,8 @@ class AppLocalizationsNe extends AppLocalizations { String get edit_port => 'पोर्ट सम्पादन गर्नुहोस्'; @override - String get port_helper_msg => 'डिफ़ॉल्ट -1 हो जुन यादृच्छिक संख्या जनाउँछ। यदि तपाईंले फायरवाल कन्फिगर गर्नुभएको छ भने, यसलाई सेट गर्न सिफारिस गरिन्छ।'; + String get port_helper_msg => + 'डिफ़ॉल्ट -1 हो जुन यादृच्छिक संख्या जनाउँछ। यदि तपाईंले फायरवाल कन्फिगर गर्नुभएको छ भने, यसलाई सेट गर्न सिफारिस गरिन्छ।'; @override String connect_request(Object client) { @@ -1343,5 +1388,6 @@ class AppLocalizationsNe extends AppLocalizations { } @override - String get connection_request_denied => 'जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।'; + String get connection_request_denied => + 'जडान अस्वीकृत। प्रयोगकर्ताले पहुँच अस्वीकृत गर्यो।'; } diff --git a/lib/l10n/generated/app_localizations_nl.dart b/lib/l10n/generated/app_localizations_nl.dart index 82d246c4..072daff7 100644 --- a/lib/l10n/generated/app_localizations_nl.dart +++ b/lib/l10n/generated/app_localizations_nl.dart @@ -407,7 +407,8 @@ class AppLocalizationsNl extends AppLocalizations { String get layout_mode => 'Opmaakmodus'; @override - String get override_layout_settings => 'Instellingen voor responsieve opmaakmodus opheffen'; + String get override_layout_settings => + 'Instellingen voor responsieve opmaakmodus opheffen'; @override String get adaptive => 'Adaptief'; @@ -437,7 +438,8 @@ class AppLocalizationsNl extends AppLocalizations { String get sync_album_color => 'Albumkleur synchroniseren'; @override - String get sync_album_color_description => 'Gebruikt de overheersende kleur van het album als accentkleur'; + String get sync_album_color_description => + 'Gebruikt de overheersende kleur van het album als accentkleur'; @override String get playback => 'Weergave'; @@ -455,7 +457,8 @@ class AppLocalizationsNl extends AppLocalizations { String get pre_download_play => 'Vooraf downloaden en afspelen'; @override - String get pre_download_play_description => 'In plaats van audio te streamen, kun je bytes downloaden en afspelen (aanbevolen voor gebruikers met een hogere bandbreedte)'; + String get pre_download_play_description => + 'In plaats van audio te streamen, kun je bytes downloaden en afspelen (aanbevolen voor gebruikers met een hogere bandbreedte)'; @override String get skip_non_music => 'Niet-muzieksegmenten overslaan (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsNl extends AppLocalizations { String get blacklist_description => 'Nummers en artiesten op de zwarte lijst'; @override - String get wait_for_download_to_finish => 'Wacht tot de huidige download is voltooid'; + String get wait_for_download_to_finish => + 'Wacht tot de huidige download is voltooid'; @override String get desktop => 'Bureaublad'; @@ -500,7 +504,8 @@ class AppLocalizationsNl extends AppLocalizations { String get please_sponsor => 'Sponsor/Doneer a.u.b.'; @override - String get spotube_description => 'Spotube, een lichtgewicht, cross-platform, vrij-voor-alles Spotify-client'; + String get spotube_description => + 'Spotube, een lichtgewicht, cross-platform, vrij-voor-alles Spotify-client'; @override String get version => 'Versie'; @@ -532,7 +537,8 @@ class AppLocalizationsNl extends AppLocalizations { String get license => 'Licentie'; @override - String get credentials_will_not_be_shared_disclaimer => 'Maak je geen zorgen, je gegevens worden niet verzameld of gedeeld met anderen.'; + String get credentials_will_not_be_shared_disclaimer => + 'Maak je geen zorgen, je gegevens worden niet verzameld of gedeeld met anderen.'; @override String get know_how_to_login => 'Weet je niet hoe je dit moet doen?'; @@ -576,19 +582,23 @@ class AppLocalizationsNl extends AppLocalizations { String get piped_instance => 'Piped-serverinstantie'; @override - String get piped_description => 'De Piped-serverinstantie die moet worden gebruikt voor overeenkomstige nummers'; + String get piped_description => + 'De Piped-serverinstantie die moet worden gebruikt voor overeenkomstige nummers'; @override - String get piped_warning => 'Sommige werken misschien niet goed. Dus gebruik ze op eigen risico'; + String get piped_warning => + 'Sommige werken misschien niet goed. Dus gebruik ze op eigen risico'; @override String get invidious_instance => 'Invidious-serverinstantie'; @override - String get invidious_description => 'De Invidious-serverinstantie die gebruikt wordt voor trackmatching'; + String get invidious_description => + 'De Invidious-serverinstantie die gebruikt wordt voor trackmatching'; @override - String get invidious_warning => 'Sommigen werken mogelijk niet goed. Gebruik op eigen risico'; + String get invidious_warning => + 'Sommigen werken mogelijk niet goed. Gebruik op eigen risico'; @override String get generate => 'Genereren'; @@ -602,7 +612,8 @@ class AppLocalizationsNl extends AppLocalizations { String get replace_downloaded_tracks => 'Alle gedownloade nummers vervangen'; @override - String get skip_download_tracks => 'Downloaden van alle gedownloade nummers overslaan'; + String get skip_download_tracks => + 'Downloaden van alle gedownloade nummers overslaan'; @override String get do_you_want_to_replace => 'Wil je het bestaande nummer vervangen?'; @@ -711,22 +722,28 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get download_warning => 'Als je alle nummers in bulk downloadt, ben je duidelijk bezig met muziekpiraterij en breng je schade toe aan de creatieve muziekmaatschappij. Ik hoop dat je je hiervan bewust bent. Probeer altijd het harde werk van artiesten te respecteren en te steunen.'; + String get download_warning => + 'Als je alle nummers in bulk downloadt, ben je duidelijk bezig met muziekpiraterij en breng je schade toe aan de creatieve muziekmaatschappij. Ik hoop dat je je hiervan bewust bent. Probeer altijd het harde werk van artiesten te respecteren en te steunen.'; @override - String get download_ip_ban_warning => 'BTW, je IP-adres kan worden geblokkeerd op YouTube als gevolg van buitensporige downloadverzoeken. IP-blokkering betekent dat je YouTube niet kunt gebruiken (zelfs als je ingelogd bent) voor tenminste 2-3 maanden vanaf dat IP-apparaat. Spotube is niet verantwoordelijk als dit ooit gebeurt.'; + String get download_ip_ban_warning => + 'BTW, je IP-adres kan worden geblokkeerd op YouTube als gevolg van buitensporige downloadverzoeken. IP-blokkering betekent dat je YouTube niet kunt gebruiken (zelfs als je ingelogd bent) voor tenminste 2-3 maanden vanaf dat IP-apparaat. Spotube is niet verantwoordelijk als dit ooit gebeurt.'; @override - String get by_clicking_accept_terms => 'Door op \'accepteren\' te klikken ga je akkoord met de volgende voorwaarden:'; + String get by_clicking_accept_terms => + 'Door op \'accepteren\' te klikken ga je akkoord met de volgende voorwaarden:'; @override - String get download_agreement_1 => 'Ik weet dat ik muziek illegaal donload. Ik ben slecht.'; + String get download_agreement_1 => + 'Ik weet dat ik muziek illegaal donload. Ik ben slecht.'; @override - String get download_agreement_2 => 'Ik steun de artiest waar ik kan en ik doe dit alleen omdat ik geen geld heb om hun kunst te kopen.'; + String get download_agreement_2 => + 'Ik steun de artiest waar ik kan en ik doe dit alleen omdat ik geen geld heb om hun kunst te kopen.'; @override - String get download_agreement_3 => 'Ik ben me er volledig van bewust dat mijn IP geblokkeerd kan worden op YouTube & ik houd Spotube of zijn eigenaars/contributeurs niet verantwoordelijk voor ongelukken die veroorzaakt worden door mijn huidige actie.'; + String get download_agreement_3 => + 'Ik ben me er volledig van bewust dat mijn IP geblokkeerd kan worden op YouTube & ik houd Spotube of zijn eigenaars/contributeurs niet verantwoordelijk voor ongelukken die veroorzaakt worden door mijn huidige actie.'; @override String get decline => 'Weigeren'; @@ -807,7 +824,8 @@ class AppLocalizationsNl extends AppLocalizations { String get failed_to_encrypt => 'Versleuteling mislukt'; @override - String get encryption_failed_warning => 'Spotube gebruikt versleuteling om je gegevens veilig op te slaan. Maar dat is niet gelukt. Dus zal het terugvallen op onveilige opslag.\nAls je linux gebruikt, zorg er dan voor dat je een geheim-dienst (gnome-keyring, kde-wallet, keepassxc etc) hebt geïnstalleerd.'; + String get encryption_failed_warning => + 'Spotube gebruikt versleuteling om je gegevens veilig op te slaan. Maar dat is niet gelukt. Dus zal het terugvallen op onveilige opslag.\nAls je linux gebruikt, zorg er dan voor dat je een geheim-dienst (gnome-keyring, kde-wallet, keepassxc etc) hebt geïnstalleerd.'; @override String get querying_info => 'Info opvragen…'; @@ -905,7 +923,8 @@ class AppLocalizationsNl extends AppLocalizations { String get friends => 'Vrienden'; @override - String get no_lyrics_available => 'Sorry, geen teksten gevonden voor dit nummer'; + String get no_lyrics_available => + 'Sorry, geen teksten gevonden voor dit nummer'; @override String get start_a_radio => 'Start een Radio'; @@ -914,7 +933,8 @@ class AppLocalizationsNl extends AppLocalizations { String get how_to_start_radio => 'Hoe wilt u de radio starten?'; @override - String get replace_queue_question => 'Wilt u de huidige wachtrij vervangen of eraan toevoegen?'; + String get replace_queue_question => + 'Wilt u de huidige wachtrij vervangen of eraan toevoegen?'; @override String get endless_playback => 'Eindeloze Afspelen'; @@ -923,7 +943,8 @@ class AppLocalizationsNl extends AppLocalizations { String get delete_playlist => 'Verwijder Afspeellijst'; @override - String get delete_playlist_confirmation => 'Weet u zeker dat u deze afspeellijst wilt verwijderen?'; + String get delete_playlist_confirmation => + 'Weet u zeker dat u deze afspeellijst wilt verwijderen?'; @override String get local_tracks => 'Lokale Nummers'; @@ -941,7 +962,8 @@ class AppLocalizationsNl extends AppLocalizations { String get freedom_of_music => '“Vrijheid van Muziek”'; @override - String get freedom_of_music_palm => '“Vrijheid van Muziek in de palm van je hand”'; + String get freedom_of_music_palm => + '“Vrijheid van Muziek in de palm van je hand”'; @override String get get_started => 'Laten we beginnen'; @@ -950,13 +972,16 @@ class AppLocalizationsNl extends AppLocalizations { String get youtube_source_description => 'Aanbevolen en werkt het beste.'; @override - String get piped_source_description => 'Voel je vrij? Hetzelfde als YouTube maar veel gratis.'; + String get piped_source_description => + 'Voel je vrij? Hetzelfde als YouTube maar veel gratis.'; @override - String get jiosaavn_source_description => 'Het beste voor de Zuid-Aziatische regio.'; + String get jiosaavn_source_description => + 'Het beste voor de Zuid-Aziatische regio.'; @override - String get invidious_source_description => 'Vergelijkbaar met Piped, maar met een hogere beschikbaarheid.'; + String get invidious_source_description => + 'Vergelijkbaar met Piped, maar met een hogere beschikbaarheid.'; @override String highest_quality(Object quality) { @@ -967,13 +992,15 @@ class AppLocalizationsNl extends AppLocalizations { String get select_audio_source => 'Selecteer Audiobron'; @override - String get endless_playback_description => 'Voeg automatisch nieuwe nummers toe aan het einde van de wachtrij'; + String get endless_playback_description => + 'Voeg automatisch nieuwe nummers toe aan het einde van de wachtrij'; @override String get choose_your_region => 'Kies uw regio'; @override - String get choose_your_region_description => 'Dit zal Spotube helpen om de juiste inhoud voor uw locatie te tonen.'; + String get choose_your_region_description => + 'Dit zal Spotube helpen om de juiste inhoud voor uw locatie te tonen.'; @override String get choose_your_language => 'Kies uw taal'; @@ -982,7 +1009,8 @@ class AppLocalizationsNl extends AppLocalizations { String get help_project_grow => 'Help dit project groeien'; @override - String get help_project_grow_description => 'Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.'; + String get help_project_grow_description => + 'Spotube is een open-source project. U kunt dit project helpen groeien door bij te dragen aan het project, bugs te melden of nieuwe functies voor te stellen.'; @override String get contribute_on_github => 'Bijdragen op GitHub'; @@ -997,7 +1025,8 @@ class AppLocalizationsNl extends AppLocalizations { String get enable_connect => 'Verbinding inschakelen'; @override - String get enable_connect_description => 'Spotube bedienen vanaf andere apparaten'; + String get enable_connect_description => + 'Spotube bedienen vanaf andere apparaten'; @override String get devices => 'Apparaten'; @@ -1076,7 +1105,8 @@ class AppLocalizationsNl extends AppLocalizations { String get choose_the_device => 'Kies het apparaat:'; @override - String get multiple_device_connected => 'Er zijn meerdere apparaten verbonden.\nKies het apparaat waarop je deze actie wilt uitvoeren'; + String get multiple_device_connected => + 'Er zijn meerdere apparaten verbonden.\nKies het apparaat waarop je deze actie wilt uitvoeren'; @override String get nothing_found => 'Niets gevonden'; @@ -1149,7 +1179,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Dit is berekend op basis van Spotify\'s uitbetaling per stream\nvan \$0.003 tot \$0.005. Dit is een hypothetische\nberekening om gebruikers inzicht te geven in hoeveel ze\naan de artiesten zouden hebben betaald als ze hun lied op Spotify zouden hebben beluisterd.'; + String get streaming_fees_hypothetical => + '*Dit is berekend op basis van Spotify\'s uitbetaling per stream\nvan \$0.003 tot \$0.005. Dit is een hypothetische\nberekening om gebruikers inzicht te geven in hoeveel ze\naan de artiesten zouden hebben betaald als ze hun lied op Spotify zouden hebben beluisterd.'; @override String get minutes_listened => 'Luistertijd'; @@ -1171,7 +1202,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1252,8 @@ class AppLocalizationsNl extends AppLocalizations { String get webview_not_found => 'Webview niet gevonden'; @override - String get webview_not_found_description => 'Er is geen Webview-runtime geïnstalleerd op uw apparaat.\nAls het is geïnstalleerd, zorg ervoor dat het in het environment PATH staat\n\nHerstart de app na installatie'; + String get webview_not_found_description => + 'Er is geen Webview-runtime geïnstalleerd op uw apparaat.\nAls het is geïnstalleerd, zorg ervoor dat het in het environment PATH staat\n\nHerstart de app na installatie'; @override String get unsupported_platform => 'Niet ondersteund platform'; @@ -1252,7 +1285,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get export_cache_confirmation => 'Wilt u deze bestanden exporteren naar'; + String get export_cache_confirmation => + 'Wilt u deze bestanden exporteren naar'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1315,22 @@ class AppLocalizationsNl extends AppLocalizations { String get view_all => 'Bekijk alles'; @override - String get no_tracks_added_yet => 'Het lijkt erop dat je nog geen nummers hebt toegevoegd'; + String get no_tracks_added_yet => + 'Het lijkt erop dat je nog geen nummers hebt toegevoegd'; @override String get no_tracks => 'Het lijkt erop dat er hier geen nummers zijn'; @override - String get no_tracks_listened_yet => 'Het lijkt erop dat je nog niets hebt beluisterd'; + String get no_tracks_listened_yet => + 'Het lijkt erop dat je nog niets hebt beluisterd'; @override String get not_following_artists => 'Je volgt geen artiesten'; @override - String get no_favorite_albums_yet => 'Het lijkt erop dat je nog geen albums aan je favorieten hebt toegevoegd'; + String get no_favorite_albums_yet => + 'Het lijkt erop dat je nog geen albums aan je favorieten hebt toegevoegd'; @override String get no_logs_found => 'Geen logbestanden gevonden'; @@ -1317,7 +1354,8 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'Op macOS/Linux/unix-achtige besturingssystemen werkt het instellen van paden in .zshrc/.bashrc/.bash_profile enz. niet.\nJe moet het pad instellen in het shell-configuratiebestand'; + String get youtube_engine_unix_issue_message => + 'Op macOS/Linux/unix-achtige besturingssystemen werkt het instellen van paden in .zshrc/.bashrc/.bash_profile enz. niet.\nJe moet het pad instellen in het shell-configuratiebestand'; @override String get download => 'Downloaden'; @@ -1335,7 +1373,8 @@ class AppLocalizationsNl extends AppLocalizations { String get edit_port => 'Poort bewerken'; @override - String get port_helper_msg => 'Standaard is -1, wat een willekeurig nummer aangeeft. Als je een firewall hebt geconfigureerd, wordt aanbevolen dit in te stellen.'; + String get port_helper_msg => + 'Standaard is -1, wat een willekeurig nummer aangeeft. Als je een firewall hebt geconfigureerd, wordt aanbevolen dit in te stellen.'; @override String connect_request(Object client) { @@ -1343,5 +1382,6 @@ class AppLocalizationsNl extends AppLocalizations { } @override - String get connection_request_denied => 'Verbinding geweigerd. Gebruiker heeft toegang geweigerd.'; + String get connection_request_denied => + 'Verbinding geweigerd. Gebruiker heeft toegang geweigerd.'; } diff --git a/lib/l10n/generated/app_localizations_pl.dart b/lib/l10n/generated/app_localizations_pl.dart index 70f253ab..51bd0283 100644 --- a/lib/l10n/generated/app_localizations_pl.dart +++ b/lib/l10n/generated/app_localizations_pl.dart @@ -407,7 +407,8 @@ class AppLocalizationsPl extends AppLocalizations { String get layout_mode => 'Tryb Układu'; @override - String get override_layout_settings => 'Nadpisz responsywne ustawienia trybu układu'; + String get override_layout_settings => + 'Nadpisz responsywne ustawienia trybu układu'; @override String get adaptive => 'Adaptacyjny'; @@ -437,7 +438,8 @@ class AppLocalizationsPl extends AppLocalizations { String get sync_album_color => 'Synchronizuj kolor albumu'; @override - String get sync_album_color_description => 'Używa dominującego koloru okładki albumu jako koloru akcentującego'; + String get sync_album_color_description => + 'Używa dominującego koloru okładki albumu jako koloru akcentującego'; @override String get playback => 'Odtwarzanie'; @@ -455,7 +457,8 @@ class AppLocalizationsPl extends AppLocalizations { String get pre_download_play => 'Wstępnie pobierz i odtwórz'; @override - String get pre_download_play_description => 'Zamiast przesyłać strumieniowo dźwięk, pobiera odpowiedni bufor i odtwarza (zalecane dla użytkowników o większej przepustowości)'; + String get pre_download_play_description => + 'Zamiast przesyłać strumieniowo dźwięk, pobiera odpowiedni bufor i odtwarza (zalecane dla użytkowników o większej przepustowości)'; @override String get skip_non_music => 'Pomiń nie-muzyczne segmenty (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsPl extends AppLocalizations { String get blacklist_description => 'Czarna lista utworów i artystów'; @override - String get wait_for_download_to_finish => 'Proszę poczekać na zakończenie obecnego pobierania.'; + String get wait_for_download_to_finish => + 'Proszę poczekać na zakończenie obecnego pobierania.'; @override String get desktop => 'Pulpit'; @@ -500,7 +504,8 @@ class AppLocalizationsPl extends AppLocalizations { String get please_sponsor => 'Proszę wesprzyj projekt'; @override - String get spotube_description => 'Spotube, lekki, wieloplatformowy, darmowy dla wszystkich klient Spotify'; + String get spotube_description => + 'Spotube, lekki, wieloplatformowy, darmowy dla wszystkich klient Spotify'; @override String get version => 'Wersja'; @@ -532,13 +537,15 @@ class AppLocalizationsPl extends AppLocalizations { String get license => 'Licencja'; @override - String get credentials_will_not_be_shared_disclaimer => 'Nie martw się, żadne dane logowania nie są zbierane ani udostępniane nikomu'; + String get credentials_will_not_be_shared_disclaimer => + 'Nie martw się, żadne dane logowania nie są zbierane ani udostępniane nikomu'; @override String get know_how_to_login => 'Nie wiesz, jak się zalogować?'; @override - String get follow_step_by_step_guide => 'Postępuj zgodnie z poradnikiem krok po kroku'; + String get follow_step_by_step_guide => + 'Postępuj zgodnie z poradnikiem krok po kroku'; @override String cookie_name_cookie(Object name) { @@ -576,19 +583,23 @@ class AppLocalizationsPl extends AppLocalizations { String get piped_instance => 'Instancja serwera Piped'; @override - String get piped_description => 'Instancja serwera Piped używana jest do dopasowania utworów.'; + String get piped_description => + 'Instancja serwera Piped używana jest do dopasowania utworów.'; @override - String get piped_warning => 'Niektóre z nich mogą nie działać. Używasz na własną odpowiedzialność!'; + String get piped_warning => + 'Niektóre z nich mogą nie działać. Używasz na własną odpowiedzialność!'; @override String get invidious_instance => 'Instancja serwera Invidious'; @override - String get invidious_description => 'Instancja serwera Invidious do dopasowywania utworów'; + String get invidious_description => + 'Instancja serwera Invidious do dopasowywania utworów'; @override - String get invidious_warning => 'Niektóre z nich mogą nie działać dobrze. Używaj na własne ryzyko'; + String get invidious_warning => + 'Niektóre z nich mogą nie działać dobrze. Używaj na własne ryzyko'; @override String get generate => 'Generuj'; @@ -602,7 +613,8 @@ class AppLocalizationsPl extends AppLocalizations { String get replace_downloaded_tracks => 'Zamień wszystkie pobrane utwory'; @override - String get skip_download_tracks => 'Pomiń pobieranie wszystkich pobranych utworów'; + String get skip_download_tracks => + 'Pomiń pobieranie wszystkich pobranych utworów'; @override String get do_you_want_to_replace => 'Chcesz zamienić istniejący utwór ??'; @@ -711,22 +723,27 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get download_warning => 'Jeśli hurtowo pobierasz wszystkie utwory, wyraźnie piracisz muzykę i wyrządzasz szkody kreatywnej społeczności muzycznej. Mam nadzieję, że jesteś tego świadomy. Zawsze staraj się szanować i wspierać ciężką pracę Artysty'; + String get download_warning => + 'Jeśli hurtowo pobierasz wszystkie utwory, wyraźnie piracisz muzykę i wyrządzasz szkody kreatywnej społeczności muzycznej. Mam nadzieję, że jesteś tego świadomy. Zawsze staraj się szanować i wspierać ciężką pracę Artysty'; @override - String get download_ip_ban_warning => 'Przy okazji, Twój adres IP może zostać zablokowany w YouTube z powodu nadmiernych żądań pobierania niż zwykle. Blokada IP oznacza, że nie możesz korzystać z YouTube (nawet jeśli jesteś zalogowany) przez co najmniej 2-3 miesiące z IP tego urządzenia. Spotube nie ponosi żadnej odpowiedzialności, jeśli tak się stanie'; + String get download_ip_ban_warning => + 'Przy okazji, Twój adres IP może zostać zablokowany w YouTube z powodu nadmiernych żądań pobierania niż zwykle. Blokada IP oznacza, że nie możesz korzystać z YouTube (nawet jeśli jesteś zalogowany) przez co najmniej 2-3 miesiące z IP tego urządzenia. Spotube nie ponosi żadnej odpowiedzialności, jeśli tak się stanie'; @override - String get by_clicking_accept_terms => 'Klikając \'Akceptuj\' zgadzasz się z następującymi warunkami:'; + String get by_clicking_accept_terms => + 'Klikając \'Akceptuj\' zgadzasz się z następującymi warunkami:'; @override String get download_agreement_1 => 'Wiem, że piracę muzykę. Jestem zły.'; @override - String get download_agreement_2 => 'Będę wspierał artystę i robię to tylko dlatego, że nie mam pieniędzy na albumy wykonawcy. '; + String get download_agreement_2 => + 'Będę wspierał artystę i robię to tylko dlatego, że nie mam pieniędzy na albumy wykonawcy. '; @override - String get download_agreement_3 => 'Jestem całkowicie świadomy, że moje IP może zostać zablokowane w YouTube i nie pociągam Spotube ani jego właścicieli/współtwórców do odpowiedzialności za jakiekolwiek wypadki spowodowane moimi obecnymi działaniami'; + String get download_agreement_3 => + 'Jestem całkowicie świadomy, że moje IP może zostać zablokowane w YouTube i nie pociągam Spotube ani jego właścicieli/współtwórców do odpowiedzialności za jakiekolwiek wypadki spowodowane moimi obecnymi działaniami'; @override String get decline => 'Odrzuć'; @@ -807,7 +824,8 @@ class AppLocalizationsPl extends AppLocalizations { String get failed_to_encrypt => 'Nie można zaszyfrować :('; @override - String get encryption_failed_warning => 'Spotube używa szyfrowania do bezpiecznego przechowywania danych. Ale nie udało się tego zrobić. Więc powróci do niezabezpieczonego przechowywania\nJeśli używasz Linuksa, upewnij się, że masz zainstalowane jakieś usługi do szyfrowania (gnome-keyring, kde-wallet, keepassxc itp.)'; + String get encryption_failed_warning => + 'Spotube używa szyfrowania do bezpiecznego przechowywania danych. Ale nie udało się tego zrobić. Więc powróci do niezabezpieczonego przechowywania\nJeśli używasz Linuksa, upewnij się, że masz zainstalowane jakieś usługi do szyfrowania (gnome-keyring, kde-wallet, keepassxc itp.)'; @override String get querying_info => 'Szukam informacji...'; @@ -824,7 +842,8 @@ class AppLocalizationsPl extends AppLocalizations { String get you_are_offline => 'Obecnie jesteś offline'; @override - String get connection_restored => 'Twoje połączenie z internetem zostało przywrócone'; + String get connection_restored => + 'Twoje połączenie z internetem zostało przywrócone'; @override String get use_system_title_bar => 'Użyj paska tytułu systemu'; @@ -905,7 +924,8 @@ class AppLocalizationsPl extends AppLocalizations { String get friends => 'Przyjaciele'; @override - String get no_lyrics_available => 'Przepraszamy, nie można znaleźć tekstu dla tego utworu'; + String get no_lyrics_available => + 'Przepraszamy, nie można znaleźć tekstu dla tego utworu'; @override String get start_a_radio => 'Uruchom radio'; @@ -914,7 +934,8 @@ class AppLocalizationsPl extends AppLocalizations { String get how_to_start_radio => 'Jak chcesz uruchomić radio?'; @override - String get replace_queue_question => 'Czy chcesz zastąpić bieżącą kolejkę czy dodać do niej?'; + String get replace_queue_question => + 'Czy chcesz zastąpić bieżącą kolejkę czy dodać do niej?'; @override String get endless_playback => 'Nieskończona Odtwarzanie'; @@ -923,7 +944,8 @@ class AppLocalizationsPl extends AppLocalizations { String get delete_playlist => 'Usuń Playlistę'; @override - String get delete_playlist_confirmation => 'Czy na pewno chcesz usunąć tę listę odtwarzania?'; + String get delete_playlist_confirmation => + 'Czy na pewno chcesz usunąć tę listę odtwarzania?'; @override String get local_tracks => 'Lokalne Utwory'; @@ -950,13 +972,16 @@ class AppLocalizationsPl extends AppLocalizations { String get youtube_source_description => 'Polecane i działa najlepiej.'; @override - String get piped_source_description => 'Czujesz się wolny? To samo co YouTube, ale dużo za darmo.'; + String get piped_source_description => + 'Czujesz się wolny? To samo co YouTube, ale dużo za darmo.'; @override - String get jiosaavn_source_description => 'Najlepszy dla regionu Azji Południowej.'; + String get jiosaavn_source_description => + 'Najlepszy dla regionu Azji Południowej.'; @override - String get invidious_source_description => 'Podobne do Piped, ale o wyższej dostępności.'; + String get invidious_source_description => + 'Podobne do Piped, ale o wyższej dostępności.'; @override String highest_quality(Object quality) { @@ -967,13 +992,15 @@ class AppLocalizationsPl extends AppLocalizations { String get select_audio_source => 'Wybierz Źródło Audio'; @override - String get endless_playback_description => 'Automatycznie dodaj nowe utwory na koniec kolejki'; + String get endless_playback_description => + 'Automatycznie dodaj nowe utwory na koniec kolejki'; @override String get choose_your_region => 'Wybierz swoją region'; @override - String get choose_your_region_description => 'To pomoże Spotube pokazać Ci odpowiednią treść dla Twojej lokalizacji.'; + String get choose_your_region_description => + 'To pomoże Spotube pokazać Ci odpowiednią treść dla Twojej lokalizacji.'; @override String get choose_your_language => 'Wybierz swój język'; @@ -982,7 +1009,8 @@ class AppLocalizationsPl extends AppLocalizations { String get help_project_grow => 'Pomóż temu projektowi rosnąć'; @override - String get help_project_grow_description => 'Spotube to projekt open-source. Możesz pomóc temu projektowi rosnąć, przyczyniając się do projektu, zgłaszając błędy lub sugerując nowe funkcje.'; + String get help_project_grow_description => + 'Spotube to projekt open-source. Możesz pomóc temu projektowi rosnąć, przyczyniając się do projektu, zgłaszając błędy lub sugerując nowe funkcje.'; @override String get contribute_on_github => 'Przyczyniaj się na GitHubie'; @@ -997,7 +1025,8 @@ class AppLocalizationsPl extends AppLocalizations { String get enable_connect => 'Włącz połączenie'; @override - String get enable_connect_description => 'Kontroluj Spotube z innych urządzeń'; + String get enable_connect_description => + 'Kontroluj Spotube z innych urządzeń'; @override String get devices => 'Urządzenia'; @@ -1076,7 +1105,8 @@ class AppLocalizationsPl extends AppLocalizations { String get choose_the_device => 'Wybierz urządzenie:'; @override - String get multiple_device_connected => 'Jest wiele urządzeń podłączonych.\nWybierz urządzenie, na którym chcesz wykonać tę akcję'; + String get multiple_device_connected => + 'Jest wiele urządzeń podłączonych.\nWybierz urządzenie, na którym chcesz wykonać tę akcję'; @override String get nothing_found => 'Nic nie znaleziono'; @@ -1149,7 +1179,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Obliczone na podstawie wypłaty Spotify za stream\nod \$0.003 do \$0.005. Jest to hipotetyczne\nobliczenie, które ma na celu pokazanie, ile\nużytkownik zapłaciłby artystom, gdyby odsłuchał\ntych utworów na Spotify.'; + String get streaming_fees_hypothetical => + '*Obliczone na podstawie wypłaty Spotify za stream\nod \$0.003 do \$0.005. Jest to hipotetyczne\nobliczenie, które ma na celu pokazanie, ile\nużytkownik zapłaciłby artystom, gdyby odsłuchał\ntych utworów na Spotify.'; @override String get minutes_listened => 'Minuty odsłuchane'; @@ -1171,7 +1202,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1252,8 @@ class AppLocalizationsPl extends AppLocalizations { String get webview_not_found => 'Nie znaleziono Webview'; @override - String get webview_not_found_description => 'Na twoim urządzeniu nie zainstalowano środowiska uruchomieniowego Webview.\nJeśli jest zainstalowany, upewnij się, że jest w environment PATH\n\nPo instalacji uruchom ponownie aplikację'; + String get webview_not_found_description => + 'Na twoim urządzeniu nie zainstalowano środowiska uruchomieniowego Webview.\nJeśli jest zainstalowany, upewnij się, że jest w environment PATH\n\nPo instalacji uruchom ponownie aplikację'; @override String get unsupported_platform => 'Nieobsługiwana platforma'; @@ -1241,7 +1274,8 @@ class AppLocalizationsPl extends AppLocalizations { String get clear_cache => 'Wyczyść pamięć podręczną'; @override - String get clear_cache_confirmation => 'Czy chcesz wyczyścić pamięć podręczną?'; + String get clear_cache_confirmation => + 'Czy chcesz wyczyścić pamięć podręczną?'; @override String get export_cache_files => 'Eksportuj pliki z pamięci podręcznej'; @@ -1252,7 +1286,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get export_cache_confirmation => 'Czy chcesz wyeksportować te pliki do'; + String get export_cache_confirmation => + 'Czy chcesz wyeksportować te pliki do'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1316,22 @@ class AppLocalizationsPl extends AppLocalizations { String get view_all => 'Zobacz wszystko'; @override - String get no_tracks_added_yet => 'Wygląda na to, że jeszcze nie dodałeś żadnych utworów'; + String get no_tracks_added_yet => + 'Wygląda na to, że jeszcze nie dodałeś żadnych utworów'; @override String get no_tracks => 'Wygląda na to, że tutaj nie ma żadnych utworów'; @override - String get no_tracks_listened_yet => 'Wygląda na to, że jeszcze nic nie słuchałeś'; + String get no_tracks_listened_yet => + 'Wygląda na to, że jeszcze nic nie słuchałeś'; @override String get not_following_artists => 'Nie obserwujesz żadnych artystów'; @override - String get no_favorite_albums_yet => 'Wygląda na to, że jeszcze nie dodałeś żadnych albumów do ulubionych'; + String get no_favorite_albums_yet => + 'Wygląda na to, że jeszcze nie dodałeś żadnych albumów do ulubionych'; @override String get no_logs_found => 'Nie znaleziono żadnych logów'; @@ -1317,7 +1355,8 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'W systemach macOS/Linux/unix, ustawianie ścieżki w .zshrc/.bashrc/.bash_profile itp. nie będzie działać.\nMusisz ustawić ścieżkę w pliku konfiguracyjnym powłoki'; + String get youtube_engine_unix_issue_message => + 'W systemach macOS/Linux/unix, ustawianie ścieżki w .zshrc/.bashrc/.bash_profile itp. nie będzie działać.\nMusisz ustawić ścieżkę w pliku konfiguracyjnym powłoki'; @override String get download => 'Pobierz'; @@ -1335,7 +1374,8 @@ class AppLocalizationsPl extends AppLocalizations { String get edit_port => 'Edytuj port'; @override - String get port_helper_msg => 'Domyślna wartość to -1, co oznacza losową liczbę. Jeśli masz skonfigurowany zaporę, zaleca się jej ustawienie.'; + String get port_helper_msg => + 'Domyślna wartość to -1, co oznacza losową liczbę. Jeśli masz skonfigurowany zaporę, zaleca się jej ustawienie.'; @override String connect_request(Object client) { @@ -1343,5 +1383,6 @@ class AppLocalizationsPl extends AppLocalizations { } @override - String get connection_request_denied => 'Połączenie odrzucone. Użytkownik odmówił dostępu.'; + String get connection_request_denied => + 'Połączenie odrzucone. Użytkownik odmówił dostępu.'; } diff --git a/lib/l10n/generated/app_localizations_pt.dart b/lib/l10n/generated/app_localizations_pt.dart index f5c17866..f4d7d57a 100644 --- a/lib/l10n/generated/app_localizations_pt.dart +++ b/lib/l10n/generated/app_localizations_pt.dart @@ -185,7 +185,8 @@ class AppLocalizationsPt extends AppLocalizations { String get follow => 'Seguir'; @override - String get artist_url_copied => 'URL do artista copiada para a área de transferência'; + String get artist_url_copied => + 'URL do artista copiada para a área de transferência'; @override String added_to_queue(Object tracks) { @@ -407,7 +408,8 @@ class AppLocalizationsPt extends AppLocalizations { String get layout_mode => 'Modo de Layout'; @override - String get override_layout_settings => 'Substituir configurações do modo de layout responsivo'; + String get override_layout_settings => + 'Substituir configurações do modo de layout responsivo'; @override String get adaptive => 'Adaptável'; @@ -437,7 +439,8 @@ class AppLocalizationsPt extends AppLocalizations { String get sync_album_color => 'Sincronizar cor do álbum'; @override - String get sync_album_color_description => 'Usa a cor predominante da capa do álbum como cor de destaque'; + String get sync_album_color_description => + 'Usa a cor predominante da capa do álbum como cor de destaque'; @override String get playback => 'Reprodução'; @@ -455,7 +458,8 @@ class AppLocalizationsPt extends AppLocalizations { String get pre_download_play => 'Pré-download e reprodução'; @override - String get pre_download_play_description => 'Em vez de transmitir áudio, baixar bytes e reproduzir (recomendado para usuários com maior largura de banda)'; + String get pre_download_play_description => + 'Em vez de transmitir áudio, baixar bytes e reproduzir (recomendado para usuários com maior largura de banda)'; @override String get skip_non_music => 'Pular segmentos não musicais (SponsorBlock)'; @@ -464,7 +468,8 @@ class AppLocalizationsPt extends AppLocalizations { String get blacklist_description => 'Faixas e artistas na lista negra'; @override - String get wait_for_download_to_finish => 'Aguarde o download atual ser concluído'; + String get wait_for_download_to_finish => + 'Aguarde o download atual ser concluído'; @override String get desktop => 'Desktop'; @@ -500,7 +505,8 @@ class AppLocalizationsPt extends AppLocalizations { String get please_sponsor => 'Por favor, patrocine/doe'; @override - String get spotube_description => 'Spotube, um cliente leve, multiplataforma e gratuito para o Spotify'; + String get spotube_description => + 'Spotube, um cliente leve, multiplataforma e gratuito para o Spotify'; @override String get version => 'Versão'; @@ -532,7 +538,8 @@ class AppLocalizationsPt extends AppLocalizations { String get license => 'Licença'; @override - String get credentials_will_not_be_shared_disclaimer => 'Não se preocupe, suas credenciais não serão coletadas nem compartilhadas com ninguém'; + String get credentials_will_not_be_shared_disclaimer => + 'Não se preocupe, suas credenciais não serão coletadas nem compartilhadas com ninguém'; @override String get know_how_to_login => 'Não sabe como fazer isso?'; @@ -576,19 +583,23 @@ class AppLocalizationsPt extends AppLocalizations { String get piped_instance => 'Instância do Servidor Piped'; @override - String get piped_description => 'A instância do servidor Piped a ser usada para correspondência de faixas'; + String get piped_description => + 'A instância do servidor Piped a ser usada para correspondência de faixas'; @override - String get piped_warning => 'Algumas delas podem não funcionar bem. Use por sua conta e risco'; + String get piped_warning => + 'Algumas delas podem não funcionar bem. Use por sua conta e risco'; @override String get invidious_instance => 'Instância do Servidor Invidious'; @override - String get invidious_description => 'A instância do servidor Invidious a ser usada para correspondência de faixas'; + String get invidious_description => + 'A instância do servidor Invidious a ser usada para correspondência de faixas'; @override - String get invidious_warning => 'Alguns podem não funcionar bem. Use por sua conta e risco'; + String get invidious_warning => + 'Alguns podem não funcionar bem. Use por sua conta e risco'; @override String get generate => 'Gerar'; @@ -602,7 +613,8 @@ class AppLocalizationsPt extends AppLocalizations { String get replace_downloaded_tracks => 'Substituir todas as faixas baixadas'; @override - String get skip_download_tracks => 'Pular o download de todas as faixas baixadas'; + String get skip_download_tracks => + 'Pular o download de todas as faixas baixadas'; @override String get do_you_want_to_replace => 'Deseja substituir a faixa existente?'; @@ -711,22 +723,28 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get download_warning => 'Se você baixar todas as faixas em massa, estará claramente pirateando música e causando danos à sociedade criativa da música. Espero que você esteja ciente disso. Sempre tente respeitar e apoiar o trabalho árduo dos artistas'; + String get download_warning => + 'Se você baixar todas as faixas em massa, estará claramente pirateando música e causando danos à sociedade criativa da música. Espero que você esteja ciente disso. Sempre tente respeitar e apoiar o trabalho árduo dos artistas'; @override - String get download_ip_ban_warning => 'Além disso, seu IP pode ser bloqueado no YouTube devido a solicitações de download excessivas. O bloqueio de IP significa que você não poderá usar o YouTube (mesmo se estiver conectado) por pelo menos 2-3 meses a partir do dispositivo IP. E o Spotube não se responsabiliza se isso acontecer'; + String get download_ip_ban_warning => + 'Além disso, seu IP pode ser bloqueado no YouTube devido a solicitações de download excessivas. O bloqueio de IP significa que você não poderá usar o YouTube (mesmo se estiver conectado) por pelo menos 2-3 meses a partir do dispositivo IP. E o Spotube não se responsabiliza se isso acontecer'; @override - String get by_clicking_accept_terms => 'Ao clicar em \'aceitar\', você concorda com os seguintes termos:'; + String get by_clicking_accept_terms => + 'Ao clicar em \'aceitar\', você concorda com os seguintes termos:'; @override - String get download_agreement_1 => 'Eu sei que estou pirateando música. Sou mau'; + String get download_agreement_1 => + 'Eu sei que estou pirateando música. Sou mau'; @override - String get download_agreement_2 => 'Vou apoiar o artista onde puder e estou fazendo isso porque não tenho dinheiro para comprar sua arte'; + String get download_agreement_2 => + 'Vou apoiar o artista onde puder e estou fazendo isso porque não tenho dinheiro para comprar sua arte'; @override - String get download_agreement_3 => 'Estou completamente ciente de que meu IP pode ser bloqueado no YouTube e não responsabilizo o Spotube ou seus proprietários/colaboradores por quaisquer acidentes causados pela minha ação atual'; + String get download_agreement_3 => + 'Estou completamente ciente de que meu IP pode ser bloqueado no YouTube e não responsabilizo o Spotube ou seus proprietários/colaboradores por quaisquer acidentes causados pela minha ação atual'; @override String get decline => 'Recusar'; @@ -807,7 +825,8 @@ class AppLocalizationsPt extends AppLocalizations { String get failed_to_encrypt => 'Falha ao criptografar'; @override - String get encryption_failed_warning => 'O Spotube usa criptografia para armazenar seus dados com segurança, mas falhou em fazê-lo. Portanto, ele voltará para o armazenamento não seguro.\nSe você estiver usando o Linux, certifique-se de ter algum serviço secreto (gnome-keyring, kde-wallet, keepassxc, etc.) instalado'; + String get encryption_failed_warning => + 'O Spotube usa criptografia para armazenar seus dados com segurança, mas falhou em fazê-lo. Portanto, ele voltará para o armazenamento não seguro.\nSe você estiver usando o Linux, certifique-se de ter algum serviço secreto (gnome-keyring, kde-wallet, keepassxc, etc.) instalado'; @override String get querying_info => 'Consultando informações...'; @@ -905,7 +924,8 @@ class AppLocalizationsPt extends AppLocalizations { String get friends => 'Amigos'; @override - String get no_lyrics_available => 'Desculpe, não foi possível encontrar a letra desta faixa'; + String get no_lyrics_available => + 'Desculpe, não foi possível encontrar a letra desta faixa'; @override String get start_a_radio => 'Iniciar uma Rádio'; @@ -914,7 +934,8 @@ class AppLocalizationsPt extends AppLocalizations { String get how_to_start_radio => 'Como você deseja iniciar a rádio?'; @override - String get replace_queue_question => 'Você deseja substituir a fila atual ou acrescentar a ela?'; + String get replace_queue_question => + 'Você deseja substituir a fila atual ou acrescentar a ela?'; @override String get endless_playback => 'Reprodução sem fim'; @@ -923,7 +944,8 @@ class AppLocalizationsPt extends AppLocalizations { String get delete_playlist => 'Excluir Lista de Reprodução'; @override - String get delete_playlist_confirmation => 'Tem certeza de que deseja excluir esta lista de reprodução?'; + String get delete_playlist_confirmation => + 'Tem certeza de que deseja excluir esta lista de reprodução?'; @override String get local_tracks => 'Faixas Locais'; @@ -941,7 +963,8 @@ class AppLocalizationsPt extends AppLocalizations { String get freedom_of_music => '“Liberdade da Música”'; @override - String get freedom_of_music_palm => '“Liberdade da Música na palma da sua mão”'; + String get freedom_of_music_palm => + '“Liberdade da Música na palma da sua mão”'; @override String get get_started => 'Vamos começar'; @@ -950,13 +973,16 @@ class AppLocalizationsPt extends AppLocalizations { String get youtube_source_description => 'Recomendado e funciona melhor.'; @override - String get piped_source_description => 'Sentindo-se livre? Igual ao YouTube, mas muito mais grátis.'; + String get piped_source_description => + 'Sentindo-se livre? Igual ao YouTube, mas muito mais grátis.'; @override - String get jiosaavn_source_description => 'Melhor para a região da Ásia do Sul.'; + String get jiosaavn_source_description => + 'Melhor para a região da Ásia do Sul.'; @override - String get invidious_source_description => 'Semelhante ao Piped, mas com maior disponibilidade.'; + String get invidious_source_description => + 'Semelhante ao Piped, mas com maior disponibilidade.'; @override String highest_quality(Object quality) { @@ -967,13 +993,15 @@ class AppLocalizationsPt extends AppLocalizations { String get select_audio_source => 'Selecionar Fonte de Áudio'; @override - String get endless_playback_description => 'Adicionar automaticamente novas músicas\nao final da fila'; + String get endless_playback_description => + 'Adicionar automaticamente novas músicas\nao final da fila'; @override String get choose_your_region => 'Escolha sua região'; @override - String get choose_your_region_description => 'Isso ajudará o Spotube a mostrar o conteúdo certo\npara sua localização.'; + String get choose_your_region_description => + 'Isso ajudará o Spotube a mostrar o conteúdo certo\npara sua localização.'; @override String get choose_your_language => 'Escolha seu idioma'; @@ -982,7 +1010,8 @@ class AppLocalizationsPt extends AppLocalizations { String get help_project_grow => 'Ajude este projeto a crescer'; @override - String get help_project_grow_description => 'Spotube é um projeto de código aberto. Você pode ajudar este projeto a crescer contribuindo para o projeto, relatando bugs ou sugerindo novos recursos.'; + String get help_project_grow_description => + 'Spotube é um projeto de código aberto. Você pode ajudar este projeto a crescer contribuindo para o projeto, relatando bugs ou sugerindo novos recursos.'; @override String get contribute_on_github => 'Contribuir no GitHub'; @@ -997,7 +1026,8 @@ class AppLocalizationsPt extends AppLocalizations { String get enable_connect => 'Ativar conexão'; @override - String get enable_connect_description => 'Controle o Spotube a partir de outros dispositivos'; + String get enable_connect_description => + 'Controle o Spotube a partir de outros dispositivos'; @override String get devices => 'Dispositivos'; @@ -1076,7 +1106,8 @@ class AppLocalizationsPt extends AppLocalizations { String get choose_the_device => 'Escolha o dispositivo:'; @override - String get multiple_device_connected => 'Há vários dispositivos conectados.\nEscolha o dispositivo no qual deseja executar esta ação'; + String get multiple_device_connected => + 'Há vários dispositivos conectados.\nEscolha o dispositivo no qual deseja executar esta ação'; @override String get nothing_found => 'Nada encontrado'; @@ -1149,7 +1180,8 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Calculado com base no pagamento por stream do Spotify\nque varia de \$0.003 a \$0.005. Isso é um cálculo hipotético\npara fornecer uma visão ao usuário sobre quanto eles\nteriam pago aos artistas se estivessem ouvindo\no seu som no Spotify.'; + String get streaming_fees_hypothetical => + '*Calculado com base no pagamento por stream do Spotify\nque varia de \$0.003 a \$0.005. Isso é um cálculo hipotético\npara fornecer uma visão ao usuário sobre quanto eles\nteriam pago aos artistas se estivessem ouvindo\no seu som no Spotify.'; @override String get minutes_listened => 'Minutos ouvidos'; @@ -1171,7 +1203,8 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1253,8 @@ class AppLocalizationsPt extends AppLocalizations { String get webview_not_found => 'Webview não encontrado'; @override - String get webview_not_found_description => 'Nenhum runtime Webview está instalado no seu dispositivo.\nSe estiver instalado, certifique-se de que está no environment PATH\n\nApós a instalação, reinicie o aplicativo'; + String get webview_not_found_description => + 'Nenhum runtime Webview está instalado no seu dispositivo.\nSe estiver instalado, certifique-se de que está no environment PATH\n\nApós a instalação, reinicie o aplicativo'; @override String get unsupported_platform => 'Plataforma não suportada'; @@ -1281,7 +1315,8 @@ class AppLocalizationsPt extends AppLocalizations { String get view_all => 'Ver tudo'; @override - String get no_tracks_added_yet => 'Parece que você ainda não adicionou nenhuma faixa'; + String get no_tracks_added_yet => + 'Parece que você ainda não adicionou nenhuma faixa'; @override String get no_tracks => 'Parece que não há faixas aqui'; @@ -1293,7 +1328,8 @@ class AppLocalizationsPt extends AppLocalizations { String get not_following_artists => 'Você não está seguindo nenhum artista'; @override - String get no_favorite_albums_yet => 'Parece que você ainda não adicionou nenhum álbum aos favoritos'; + String get no_favorite_albums_yet => + 'Parece que você ainda não adicionou nenhum álbum aos favoritos'; @override String get no_logs_found => 'Nenhum log encontrado'; @@ -1317,7 +1353,8 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'Em sistemas macOS/Linux/unix, definir o caminho no .zshrc/.bashrc/.bash_profile etc. não funcionará.\nVocê precisa definir o caminho no arquivo de configuração do shell'; + String get youtube_engine_unix_issue_message => + 'Em sistemas macOS/Linux/unix, definir o caminho no .zshrc/.bashrc/.bash_profile etc. não funcionará.\nVocê precisa definir o caminho no arquivo de configuração do shell'; @override String get download => 'Baixar'; @@ -1335,7 +1372,8 @@ class AppLocalizationsPt extends AppLocalizations { String get edit_port => 'Editar porta'; @override - String get port_helper_msg => 'O padrão é -1, que indica um número aleatório. Se você tiver um firewall configurado, é recomendável definir isso.'; + String get port_helper_msg => + 'O padrão é -1, que indica um número aleatório. Se você tiver um firewall configurado, é recomendável definir isso.'; @override String connect_request(Object client) { @@ -1343,5 +1381,6 @@ class AppLocalizationsPt extends AppLocalizations { } @override - String get connection_request_denied => 'Conexão negada. O usuário negou o acesso .'; + String get connection_request_denied => + 'Conexão negada. O usuário negou o acesso .'; } diff --git a/lib/l10n/generated/app_localizations_ru.dart b/lib/l10n/generated/app_localizations_ru.dart index 7ee9f50b..7403a674 100644 --- a/lib/l10n/generated/app_localizations_ru.dart +++ b/lib/l10n/generated/app_localizations_ru.dart @@ -185,7 +185,8 @@ class AppLocalizationsRu extends AppLocalizations { String get follow => 'Подписаться'; @override - String get artist_url_copied => 'URL-адрес исполнителя скопирован в буфер обмена'; + String get artist_url_copied => + 'URL-адрес исполнителя скопирован в буфер обмена'; @override String added_to_queue(Object tracks) { @@ -407,7 +408,8 @@ class AppLocalizationsRu extends AppLocalizations { String get layout_mode => 'Режим компоновки'; @override - String get override_layout_settings => 'Изменить настройки режима адаптивной компоновки'; + String get override_layout_settings => + 'Изменить настройки режима адаптивной компоновки'; @override String get adaptive => 'Адаптивный'; @@ -437,7 +439,8 @@ class AppLocalizationsRu extends AppLocalizations { String get sync_album_color => 'Синхронизировать цвет альбома'; @override - String get sync_album_color_description => 'Использует основной цвет обложки альбома как цвет акцента'; + String get sync_album_color_description => + 'Использует основной цвет обложки альбома как цвет акцента'; @override String get playback => 'Воспроизведение'; @@ -455,16 +458,19 @@ class AppLocalizationsRu extends AppLocalizations { String get pre_download_play => 'Предварительная загрузка и воспроизведение'; @override - String get pre_download_play_description => 'Вместо потоковой передачи аудио используйте загруженные байты и воспроизводьте их (рекомендуется для пользователей с высокой пропускной способностью)'; + String get pre_download_play_description => + 'Вместо потоковой передачи аудио используйте загруженные байты и воспроизводьте их (рекомендуется для пользователей с высокой пропускной способностью)'; @override - String get skip_non_music => 'Пропускать немузыкальные сегменты (SponsorBlock)'; + String get skip_non_music => + 'Пропускать немузыкальные сегменты (SponsorBlock)'; @override String get blacklist_description => 'Черный список треков и артистов'; @override - String get wait_for_download_to_finish => 'Пожалуйста, дождитесь завершения текущей загрузки'; + String get wait_for_download_to_finish => + 'Пожалуйста, дождитесь завершения текущей загрузки'; @override String get desktop => 'Компьютер'; @@ -500,7 +506,8 @@ class AppLocalizationsRu extends AppLocalizations { String get please_sponsor => 'Стать спосором/поддержать'; @override - String get spotube_description => 'Spotube – это легкий, кросс-платформенный клиент Spotify, предоставляющий бесплатный доступ для всех пользователей'; + String get spotube_description => + 'Spotube – это легкий, кросс-платформенный клиент Spotify, предоставляющий бесплатный доступ для всех пользователей'; @override String get version => 'Версия'; @@ -532,7 +539,8 @@ class AppLocalizationsRu extends AppLocalizations { String get license => 'Лицензия'; @override - String get credentials_will_not_be_shared_disclaimer => 'Не беспокойся, никакая личная информация не собирается и не передается'; + String get credentials_will_not_be_shared_disclaimer => + 'Не беспокойся, никакая личная информация не собирается и не передается'; @override String get know_how_to_login => 'Не знаете, как это сделать?'; @@ -576,19 +584,23 @@ class AppLocalizationsRu extends AppLocalizations { String get piped_instance => 'Экземпляр сервера Piped'; @override - String get piped_description => 'Серверный экземпляр Piped для сопоставления треков'; + String get piped_description => + 'Серверный экземпляр Piped для сопоставления треков'; @override - String get piped_warning => 'Некоторые из них могут работать неправильно, поэтому используйте на свой страх и риск'; + String get piped_warning => + 'Некоторые из них могут работать неправильно, поэтому используйте на свой страх и риск'; @override String get invidious_instance => 'Экземпляр сервера Invidious'; @override - String get invidious_description => 'Экземпляр сервера Invidious для сопоставления треков'; + String get invidious_description => + 'Экземпляр сервера Invidious для сопоставления треков'; @override - String get invidious_warning => 'Некоторые могут работать не очень хорошо. Используйте на свой страх и риск'; + String get invidious_warning => + 'Некоторые могут работать не очень хорошо. Используйте на свой страх и риск'; @override String get generate => 'Генерировать'; @@ -602,7 +614,8 @@ class AppLocalizationsRu extends AppLocalizations { String get replace_downloaded_tracks => 'Заменить все ранее скачанные треки'; @override - String get skip_download_tracks => 'Пропустить загрузку всех ранее скачанных треков'; + String get skip_download_tracks => + 'Пропустить загрузку всех ранее скачанных треков'; @override String get do_you_want_to_replace => 'Хотите заменить существующий трек??'; @@ -711,22 +724,28 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get download_warning => 'При скачивании всех треков пакетом вы фактически занимаетесь пиратством и наносите ущерб творческому обществу музыки. Надеюсь, что вы осознаете это. Всегда старайтесь уважать и поддерживать усилия исполнителей, вложенные в их творчество'; + String get download_warning => + 'При скачивании всех треков пакетом вы фактически занимаетесь пиратством и наносите ущерб творческому обществу музыки. Надеюсь, что вы осознаете это. Всегда старайтесь уважать и поддерживать усилия исполнителей, вложенные в их творчество'; @override - String get download_ip_ban_warning => 'Кроме того, стоит учитывать, что из-за чрезмерного количества запросов на скачивание ваш IP-адрес может быть заблокирован на YouTube. Блокировка IP означает, что вы не сможете использовать YouTube (даже если вы вошли в свою учетную запись) в течение, как минимум, 2-3 месяцев с того устройства, с которого были сделаны эти запросы. Важно заметить, что Spotube не несет ответственности за такие события'; + String get download_ip_ban_warning => + 'Кроме того, стоит учитывать, что из-за чрезмерного количества запросов на скачивание ваш IP-адрес может быть заблокирован на YouTube. Блокировка IP означает, что вы не сможете использовать YouTube (даже если вы вошли в свою учетную запись) в течение, как минимум, 2-3 месяцев с того устройства, с которого были сделаны эти запросы. Важно заметить, что Spotube не несет ответственности за такие события'; @override - String get by_clicking_accept_terms => 'Нажимая \'принять\', вы соглашаетесь с следующими условиями:'; + String get by_clicking_accept_terms => + 'Нажимая \'принять\', вы соглашаетесь с следующими условиями:'; @override - String get download_agreement_1 => 'Я осознаю, что я использую музыку незаконно. Это плохо.'; + String get download_agreement_1 => + 'Я осознаю, что я использую музыку незаконно. Это плохо.'; @override - String get download_agreement_2 => 'Я бы поддержал исполнителей, где только смог, и делаю это, так как не имею средств на приобретение их творчества'; + String get download_agreement_2 => + 'Я бы поддержал исполнителей, где только смог, и делаю это, так как не имею средств на приобретение их творчества'; @override - String get download_agreement_3 => 'Я полностью осознаю, что мой IP-адрес может быть заблокирован на YouTube, и я не считаю Spotube или его владельцев/соавторов ответственными за какие-либо неприятности, вызванные моими текущими действиями'; + String get download_agreement_3 => + 'Я полностью осознаю, что мой IP-адрес может быть заблокирован на YouTube, и я не считаю Spotube или его владельцев/соавторов ответственными за какие-либо неприятности, вызванные моими текущими действиями'; @override String get decline => 'Отклонить'; @@ -807,7 +826,8 @@ class AppLocalizationsRu extends AppLocalizations { String get failed_to_encrypt => 'Не удалось зашифровать'; @override - String get encryption_failed_warning => 'Spotube использует шифрование для безопасного хранения ваших данных. Однако в этом случае произошла ошибка. Поэтому будет использовано небезопасное хранилище.\nЕсли вы используете Linux, убедитесь, что у вас установлен какой-либо инструмент для работы с секретами (gnome-keyring, kde-wallet, keepassxc и т.д.)'; + String get encryption_failed_warning => + 'Spotube использует шифрование для безопасного хранения ваших данных. Однако в этом случае произошла ошибка. Поэтому будет использовано небезопасное хранилище.\nЕсли вы используете Linux, убедитесь, что у вас установлен какой-либо инструмент для работы с секретами (gnome-keyring, kde-wallet, keepassxc и т.д.)'; @override String get querying_info => 'Запрос информации...'; @@ -905,7 +925,8 @@ class AppLocalizationsRu extends AppLocalizations { String get friends => 'Друзья'; @override - String get no_lyrics_available => 'Извините, не удается найти текст для этого трека'; + String get no_lyrics_available => + 'Извините, не удается найти текст для этого трека'; @override String get start_a_radio => 'Запустить радио'; @@ -914,7 +935,8 @@ class AppLocalizationsRu extends AppLocalizations { String get how_to_start_radio => 'Как вы хотите запустить радио?'; @override - String get replace_queue_question => 'Хотите заменить текущую очередь или добавить к ней?'; + String get replace_queue_question => + 'Хотите заменить текущую очередь или добавить к ней?'; @override String get endless_playback => 'Бесконечное воспроизведение'; @@ -923,7 +945,8 @@ class AppLocalizationsRu extends AppLocalizations { String get delete_playlist => 'Удалить плейлист'; @override - String get delete_playlist_confirmation => 'Вы уверены, что хотите удалить этот плейлист?'; + String get delete_playlist_confirmation => + 'Вы уверены, что хотите удалить этот плейлист?'; @override String get local_tracks => 'Локальные треки'; @@ -947,16 +970,20 @@ class AppLocalizationsRu extends AppLocalizations { String get get_started => 'Начнем'; @override - String get youtube_source_description => 'Рекомендуется и лучше всего работает.'; + String get youtube_source_description => + 'Рекомендуется и лучше всего работает.'; @override - String get piped_source_description => 'Чувствуете себя свободно? То же самое, что и YouTube, но намного бесплатно.'; + String get piped_source_description => + 'Чувствуете себя свободно? То же самое, что и YouTube, но намного бесплатно.'; @override - String get jiosaavn_source_description => 'Лучший для Южно-Азиатского региона.'; + String get jiosaavn_source_description => + 'Лучший для Южно-Азиатского региона.'; @override - String get invidious_source_description => 'Похож на Piped, но с более высокой доступностью.'; + String get invidious_source_description => + 'Похож на Piped, но с более высокой доступностью.'; @override String highest_quality(Object quality) { @@ -967,13 +994,15 @@ class AppLocalizationsRu extends AppLocalizations { String get select_audio_source => 'Выберите аудиоисточник'; @override - String get endless_playback_description => 'Автоматически добавляйте новые песни\nв конец очереди'; + String get endless_playback_description => + 'Автоматически добавляйте новые песни\nв конец очереди'; @override String get choose_your_region => 'Выберите ваш регион'; @override - String get choose_your_region_description => 'Это поможет Spotube показать вам правильный контент\nдля вашего местоположения.'; + String get choose_your_region_description => + 'Это поможет Spotube показать вам правильный контент\nдля вашего местоположения.'; @override String get choose_your_language => 'Выберите ваш язык'; @@ -982,7 +1011,8 @@ class AppLocalizationsRu extends AppLocalizations { String get help_project_grow => 'Помогите этому проекту расти'; @override - String get help_project_grow_description => 'Spotube - это проект с открытым исходным кодом. Вы можете помочь этому проекту развиваться, внося вклад в проект, сообщая ошибках или предлагая новые функции.'; + String get help_project_grow_description => + 'Spotube - это проект с открытым исходным кодом. Вы можете помочь этому проекту развиваться, внося вклад в проект, сообщая ошибках или предлагая новые функции.'; @override String get contribute_on_github => 'Внести вклад на GitHub'; @@ -997,7 +1027,8 @@ class AppLocalizationsRu extends AppLocalizations { String get enable_connect => 'Включить подключение'; @override - String get enable_connect_description => 'Управление Spotube с других устройств'; + String get enable_connect_description => + 'Управление Spotube с других устройств'; @override String get devices => 'Устройства'; @@ -1076,7 +1107,8 @@ class AppLocalizationsRu extends AppLocalizations { String get choose_the_device => 'Выберите устройство:'; @override - String get multiple_device_connected => 'Подключено несколько устройств.\nВыберите устройство, на котором вы хотите выполнить это действие'; + String get multiple_device_connected => + 'Подключено несколько устройств.\nВыберите устройство, на котором вы хотите выполнить это действие'; @override String get nothing_found => 'Ничего не найдено'; @@ -1149,7 +1181,8 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Рассчитано на основе выплат Spotify за стрим\nот \$0.003 до \$0.005. Это гипотетический\nрасчет, чтобы показать пользователю, сколько бы он\nзаплатил артистам, если бы слушал их песни на Spotify.'; + String get streaming_fees_hypothetical => + '*Рассчитано на основе выплат Spotify за стрим\nот \$0.003 до \$0.005. Это гипотетический\nрасчет, чтобы показать пользователю, сколько бы он\nзаплатил артистам, если бы слушал их песни на Spotify.'; @override String get minutes_listened => 'Минут прослушивания'; @@ -1171,7 +1204,8 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1254,8 @@ class AppLocalizationsRu extends AppLocalizations { String get webview_not_found => 'Webview не найден'; @override - String get webview_not_found_description => 'На вашем устройстве не установлена среда выполнения Webview.\nЕсли он установлен, убедитесь, что он находится в environment PATH\n\nПосле установки перезапустите приложение'; + String get webview_not_found_description => + 'На вашем устройстве не установлена среда выполнения Webview.\nЕсли он установлен, убедитесь, что он находится в environment PATH\n\nПосле установки перезапустите приложение'; @override String get unsupported_platform => 'Платформа не поддерживается'; @@ -1252,7 +1287,8 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get export_cache_confirmation => 'Вы хотите экспортировать эти файлы в'; + String get export_cache_confirmation => + 'Вы хотите экспортировать эти файлы в'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,7 +1317,8 @@ class AppLocalizationsRu extends AppLocalizations { String get view_all => 'Просмотреть все'; @override - String get no_tracks_added_yet => 'Похоже, вы ещё не добавили ни одного трека'; + String get no_tracks_added_yet => + 'Похоже, вы ещё не добавили ни одного трека'; @override String get no_tracks => 'Похоже, здесь нет треков'; @@ -1293,7 +1330,8 @@ class AppLocalizationsRu extends AppLocalizations { String get not_following_artists => 'Вы не подписаны на художников'; @override - String get no_favorite_albums_yet => 'Похоже, вы ещё не добавили ни одного альбома в избранное'; + String get no_favorite_albums_yet => + 'Похоже, вы ещё не добавили ни одного альбома в избранное'; @override String get no_logs_found => 'Логи не найдены'; @@ -1317,7 +1355,8 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'В macOS/Linux/Unix-подобных ОС, установка пути в .zshrc/.bashrc/.bash_profile и т.д. не будет работать.\nВы должны установить путь в файле конфигурации оболочки'; + String get youtube_engine_unix_issue_message => + 'В macOS/Linux/Unix-подобных ОС, установка пути в .zshrc/.bashrc/.bash_profile и т.д. не будет работать.\nВы должны установить путь в файле конфигурации оболочки'; @override String get download => 'Скачать'; @@ -1335,7 +1374,8 @@ class AppLocalizationsRu extends AppLocalizations { String get edit_port => 'Редактировать порт'; @override - String get port_helper_msg => 'По умолчанию -1, что означает случайное число. Если у вас настроен брандмауэр, рекомендуется установить это.'; + String get port_helper_msg => + 'По умолчанию -1, что означает случайное число. Если у вас настроен брандмауэр, рекомендуется установить это.'; @override String connect_request(Object client) { @@ -1343,5 +1383,6 @@ class AppLocalizationsRu extends AppLocalizations { } @override - String get connection_request_denied => 'Подключение отклонено. Пользователь отказал в доступе.'; + String get connection_request_denied => + 'Подключение отклонено. Пользователь отказал в доступе.'; } diff --git a/lib/l10n/generated/app_localizations_ta.dart b/lib/l10n/generated/app_localizations_ta.dart index f19c709a..22507186 100644 --- a/lib/l10n/generated/app_localizations_ta.dart +++ b/lib/l10n/generated/app_localizations_ta.dart @@ -185,7 +185,8 @@ class AppLocalizationsTa extends AppLocalizations { String get follow => 'பின்தொடர்'; @override - String get artist_url_copied => 'கலைஞர் URL கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது'; + String get artist_url_copied => + 'கலைஞர் URL கிளிப்போர்டுக்கு நகலெடுக்கப்பட்டது'; @override String added_to_queue(Object tracks) { @@ -437,7 +438,8 @@ class AppLocalizationsTa extends AppLocalizations { String get sync_album_color => 'ஆல்பம் நிறத்தை ஒத்திசை'; @override - String get sync_album_color_description => 'ஆல்பம் படத்தின் முக்கிய நிறத்தை அழுத்த நிறமாகப் பயன்படுத்துகிறது'; + String get sync_album_color_description => + 'ஆல்பம் படத்தின் முக்கிய நிறத்தை அழுத்த நிறமாகப் பயன்படுத்துகிறது'; @override String get playback => 'பின்னணி'; @@ -455,16 +457,19 @@ class AppLocalizationsTa extends AppLocalizations { String get pre_download_play => 'முன்பதிவிறக்கம் மற்றும் இயக்கம்'; @override - String get pre_download_play_description => 'ஒலியை ஸ்ட்ரீம் செய்வதற்குப் பதிலாக, பைட்டுகளைப் பதிவிறக்கி இயக்கவும் (அதிக பேண்ட்விட்த் பயனர்களுக்கு பரிந்துரைக்கப்படுகிறது)'; + String get pre_download_play_description => + 'ஒலியை ஸ்ட்ரீம் செய்வதற்குப் பதிலாக, பைட்டுகளைப் பதிவிறக்கி இயக்கவும் (அதிக பேண்ட்விட்த் பயனர்களுக்கு பரிந்துரைக்கப்படுகிறது)'; @override String get skip_non_music => 'இசையல்லாத பகுதிகளைத் தவிர் (SponsorBlock)'; @override - String get blacklist_description => 'தடைசெய்யப்பட்ட பாடல்கள் மற்றும் கலைஞர்கள்'; + String get blacklist_description => + 'தடைசெய்யப்பட்ட பாடல்கள் மற்றும் கலைஞர்கள்'; @override - String get wait_for_download_to_finish => 'தற்போதைய பதிவிறக்கம் முடியும் வரை காத்திருக்கவும்'; + String get wait_for_download_to_finish => + 'தற்போதைய பதிவிறக்கம் முடியும் வரை காத்திருக்கவும்'; @override String get desktop => 'கணினி'; @@ -485,7 +490,8 @@ class AppLocalizationsTa extends AppLocalizations { String get about => 'பற்றி'; @override - String get u_love_spotube => 'நீங்கள் Spotube ஐ நேசிக்கிறீர்கள் என்பது எங்களுக்குத் தெரியும்'; + String get u_love_spotube => + 'நீங்கள் Spotube ஐ நேசிக்கிறீர்கள் என்பது எங்களுக்குத் தெரியும்'; @override String get check_for_updates => 'புதுப்பிப்புகளைச் சரிபார்'; @@ -500,7 +506,8 @@ class AppLocalizationsTa extends AppLocalizations { String get please_sponsor => 'தயவுசெய்து ஆதரவு/நன்கொடை அளியுங்கள்'; @override - String get spotube_description => 'Spotube, ஒரு லேசான, பல தளங்களில் இயங்கும், அனைவருக்கும் இலவசமான spotify கிளையன்ட்'; + String get spotube_description => + 'Spotube, ஒரு லேசான, பல தளங்களில் இயங்கும், அனைவருக்கும் இலவசமான spotify கிளையன்ட்'; @override String get version => 'பதிப்பு'; @@ -532,13 +539,15 @@ class AppLocalizationsTa extends AppLocalizations { String get license => 'உரிமம்'; @override - String get credentials_will_not_be_shared_disclaimer => 'கவலைப்பட வேண்டாம், உங்கள் சான்றுகள் எதுவும் சேகரிக்கப்படாது அல்லது யாருடனும் பகிரப்படாது'; + String get credentials_will_not_be_shared_disclaimer => + 'கவலைப்பட வேண்டாம், உங்கள் சான்றுகள் எதுவும் சேகரிக்கப்படாது அல்லது யாருடனும் பகிரப்படாது'; @override String get know_how_to_login => 'இதை எப்படி செய்வது என்று தெரியவில்லையா?'; @override - String get follow_step_by_step_guide => 'படிப்படியான வழிகாட்டியைப் பின்பற்றவும்'; + String get follow_step_by_step_guide => + 'படிப்படியான வழிகாட்டியைப் பின்பற்றவும்'; @override String cookie_name_cookie(Object name) { @@ -576,19 +585,23 @@ class AppLocalizationsTa extends AppLocalizations { String get piped_instance => 'Piped சேவையகம் நிகழ்வு'; @override - String get piped_description => 'பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Piped சேவையகம் நிகழ்வு'; + String get piped_description => + 'பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Piped சேவையகம் நிகழ்வு'; @override - String get piped_warning => 'அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்'; + String get piped_warning => + 'அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்'; @override String get invidious_instance => 'Invidious சேவையக நிகழ்வு'; @override - String get invidious_description => 'பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Invidious சேவையக நிகழ்வு'; + String get invidious_description => + 'பாடல் பொருத்தத்திற்குப் பயன்படுத்த வேண்டிய Invidious சேவையக நிகழ்வு'; @override - String get invidious_warning => 'அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்'; + String get invidious_warning => + 'அவற்றில் சில நன்றாக வேலை செய்யாமல் இருக்கலாம். எனவே உங்கள் சொந்த ஆபத்தில் பயன்படுத்தவும்'; @override String get generate => 'உருவாக்கு'; @@ -599,13 +612,16 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் மாற்றவும்'; + String get replace_downloaded_tracks => + 'பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் மாற்றவும்'; @override - String get skip_download_tracks => 'பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் தவிர்க்கவும்'; + String get skip_download_tracks => + 'பதிவிறக்கம் செய்யப்பட்ட அனைத்து பாடல்களையும் தவிர்க்கவும்'; @override - String get do_you_want_to_replace => 'ஏற்கனவே உள்ள பாடலை மாற்ற விரும்புகிறீர்களா?'; + String get do_you_want_to_replace => + 'ஏற்கனவே உள்ள பாடலை மாற்ற விரும்புகிறீர்களா?'; @override String get replace => 'மாற்று'; @@ -628,7 +644,8 @@ class AppLocalizationsTa extends AppLocalizations { String get country => 'நாடு'; @override - String get number_of_tracks_generate => 'உருவாக்க வேண்டிய பாடல்களின் எண்ணிக்கை'; + String get number_of_tracks_generate => + 'உருவாக்க வேண்டிய பாடல்களின் எண்ணிக்கை'; @override String get acousticness => 'அகவுஸ்டிக்னெஸ்'; @@ -703,7 +720,8 @@ class AppLocalizationsTa extends AppLocalizations { String get are_you_sure => 'உறுதியாக இருக்கிறீர்களா?'; @override - String get generating_playlist => 'உங்கள் தனிப்பயன்பாட்டிற்கான பாடல் பட்டியலை உருவாக்குகிறது...'; + String get generating_playlist => + 'உங்கள் தனிப்பயன்பாட்டிற்கான பாடல் பட்டியலை உருவாக்குகிறது...'; @override String selected_count_tracks(Object count) { @@ -711,22 +729,28 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get download_warning => 'நீங்கள் அனைத்து பாடல்களையும் மொத்தமாக பதிவிறக்கினால், நீங்கள் தெளிவாக இசையைத் திருடுகிறீர்கள் மற்றும் இசையின் படைப்பாற்றல் சமூகத்திற்கு சேதம் விளைவிக்கிறீர்கள். நீங்கள் இதை அறிந்திருக்கிறீர்கள் என்று நம்புகிறேன். எப்போதும், கலைஞரின் கடின உழைப்பை மதித்து ஆதரிக்க முயற்சி செய்யுங்கள்'; + String get download_warning => + 'நீங்கள் அனைத்து பாடல்களையும் மொத்தமாக பதிவிறக்கினால், நீங்கள் தெளிவாக இசையைத் திருடுகிறீர்கள் மற்றும் இசையின் படைப்பாற்றல் சமூகத்திற்கு சேதம் விளைவிக்கிறீர்கள். நீங்கள் இதை அறிந்திருக்கிறீர்கள் என்று நம்புகிறேன். எப்போதும், கலைஞரின் கடின உழைப்பை மதித்து ஆதரிக்க முயற்சி செய்யுங்கள்'; @override - String get download_ip_ban_warning => 'மேலும், அதிகப்படியான பதிவிறக்க கோரிக்கைகள் காரணமாக உங்கள் IP YouTube இல் தடைசெய்யப்படலாம். IP தடை என்பது குறைந்தது 2-3 மாதங்களுக்கு அந்த IP சாதனத்திலிருந்து YouTube ஐப் பயன்படுத்த முடியாது (நீங்கள் உள்நுழைந்திருந்தாலும் கூட). இது ஒருபோதும் நடந்தால் Spotube பொறுப்பேற்காது'; + String get download_ip_ban_warning => + 'மேலும், அதிகப்படியான பதிவிறக்க கோரிக்கைகள் காரணமாக உங்கள் IP YouTube இல் தடைசெய்யப்படலாம். IP தடை என்பது குறைந்தது 2-3 மாதங்களுக்கு அந்த IP சாதனத்திலிருந்து YouTube ஐப் பயன்படுத்த முடியாது (நீங்கள் உள்நுழைந்திருந்தாலும் கூட). இது ஒருபோதும் நடந்தால் Spotube பொறுப்பேற்காது'; @override - String get by_clicking_accept_terms => '\'ஏற்றுக்கொள்\' என்பதைக் கிளிக் செய்வதன் மூலம் பின்வரும் விதிமுறைகளுக்கு நீங்கள் ஒப்புக்கொள்கிறீர்கள்:'; + String get by_clicking_accept_terms => + '\'ஏற்றுக்கொள்\' என்பதைக் கிளிக் செய்வதன் மூலம் பின்வரும் விதிமுறைகளுக்கு நீங்கள் ஒப்புக்கொள்கிறீர்கள்:'; @override - String get download_agreement_1 => 'நான் இசையைத் திருடுகிறேன் என்பது எனக்குத் தெரியும். நான் கெட்டவன்'; + String get download_agreement_1 => + 'நான் இசையைத் திருடுகிறேன் என்பது எனக்குத் தெரியும். நான் கெட்டவன்'; @override - String get download_agreement_2 => 'நான் கலைஞரை முடிந்தவரை ஆதரிப்பேன், அவர்களின் கலைக்கு பணம் செலுத்த எனக்கு பணம் இல்லாததால் மட்டுமே இதைச் செய்கிறேன்'; + String get download_agreement_2 => + 'நான் கலைஞரை முடிந்தவரை ஆதரிப்பேன், அவர்களின் கலைக்கு பணம் செலுத்த எனக்கு பணம் இல்லாததால் மட்டுமே இதைச் செய்கிறேன்'; @override - String get download_agreement_3 => 'என் IP YouTube இல் தடைசெய்யப்படலாம் என்பதை நான் முழுமையாக அறிவேன், மேலும் என் தற்போதைய செயலால் ஏற்படும் எந்த விபத்துகளுக்கும் Spotube அல்லது அதன் உரிமையாளர்கள்/பங்களிப்பாளர்களை பொறுப்பாக்க மாட்டேன்'; + String get download_agreement_3 => + 'என் IP YouTube இல் தடைசெய்யப்படலாம் என்பதை நான் முழுமையாக அறிவேன், மேலும் என் தற்போதைய செயலால் ஏற்படும் எந்த விபத்துகளுக்கும் Spotube அல்லது அதன் உரிமையாளர்கள்/பங்களிப்பாளர்களை பொறுப்பாக்க மாட்டேன்'; @override String get decline => 'மறு'; @@ -807,7 +831,8 @@ class AppLocalizationsTa extends AppLocalizations { String get failed_to_encrypt => 'குறியாக்கம் தோல்வியடைந்தது'; @override - String get encryption_failed_warning => 'Spotube உங்கள் தரவை பாதுகாப்பாக சேமிக்க குறியாக்கத்தைப் பயன்படுத்துகிறது. ஆனால் அவ்வாறு செய்ய முடியவில்லை. எனவே இது பாதுகாப்பற்ற சேமிப்பகத்திற்கு மாறும்\nநீங்கள் லினக்ஸ் பயன்படுத்துகிறீர்கள் என்றால், எந்த ரகசிய சேவையும் (gnome-keyring, kde-wallet, keepassxc போன்றவை) நிறுவப்பட்டுள்ளதா என்பதை உறுதிப்படுத்தவும்'; + String get encryption_failed_warning => + 'Spotube உங்கள் தரவை பாதுகாப்பாக சேமிக்க குறியாக்கத்தைப் பயன்படுத்துகிறது. ஆனால் அவ்வாறு செய்ய முடியவில்லை. எனவே இது பாதுகாப்பற்ற சேமிப்பகத்திற்கு மாறும்\nநீங்கள் லினக்ஸ் பயன்படுத்துகிறீர்கள் என்றால், எந்த ரகசிய சேவையும் (gnome-keyring, kde-wallet, keepassxc போன்றவை) நிறுவப்பட்டுள்ளதா என்பதை உறுதிப்படுத்தவும்'; @override String get querying_info => 'தகவலைக் கேட்கிறது...'; @@ -905,7 +930,8 @@ class AppLocalizationsTa extends AppLocalizations { String get friends => 'நண்பர்கள்'; @override - String get no_lyrics_available => 'மன்னிக்கவும், இந்தப் பாடலுக்கான பாடல் வரிகளைக் கண்டுபிடிக்க முடியவில்லை'; + String get no_lyrics_available => + 'மன்னிக்கவும், இந்தப் பாடலுக்கான பாடல் வரிகளைக் கண்டுபிடிக்க முடியவில்லை'; @override String get start_a_radio => 'வானொலியைத் தொடங்கு'; @@ -914,7 +940,8 @@ class AppLocalizationsTa extends AppLocalizations { String get how_to_start_radio => 'வானொலியை எவ்வாறு தொடங்க விரும்புகிறீர்கள்?'; @override - String get replace_queue_question => 'தற்போதைய வரிசையை மாற்ற விரும்புகிறீர்களா அல்லது அதனுடன் சேர்க்க விரும்புகிறீர்களா?'; + String get replace_queue_question => + 'தற்போதைய வரிசையை மாற்ற விரும்புகிறீர்களா அல்லது அதனுடன் சேர்க்க விரும்புகிறீர்களா?'; @override String get endless_playback => 'முடிவற்ற இயக்கம்'; @@ -923,7 +950,8 @@ class AppLocalizationsTa extends AppLocalizations { String get delete_playlist => 'பாடல் பட்டியலை நீக்கு'; @override - String get delete_playlist_confirmation => 'இந்த பாடல் பட்டியலை நீக்க விரும்புகிறீர்களா?'; + String get delete_playlist_confirmation => + 'இந்த பாடல் பட்டியலை நீக்க விரும்புகிறீர்களா?'; @override String get local_tracks => 'உள்ளூர் பாடல்கள்'; @@ -947,16 +975,20 @@ class AppLocalizationsTa extends AppLocalizations { String get get_started => 'தொடங்குவோம்'; @override - String get youtube_source_description => 'பரிந்துரைக்கப்படுகிறது மற்றும் சிறப்பாக செயல்படுகிறது.'; + String get youtube_source_description => + 'பரிந்துரைக்கப்படுகிறது மற்றும் சிறப்பாக செயல்படுகிறது.'; @override - String get piped_source_description => 'சுதந்திரமாக உணர்கிறீர்களா? YouTube போலவே ஆனால் மிகவும் சுதந்திரமானது.'; + String get piped_source_description => + 'சுதந்திரமாக உணர்கிறீர்களா? YouTube போலவே ஆனால் மிகவும் சுதந்திரமானது.'; @override - String get jiosaavn_source_description => 'தெற்காசியப் பிராந்தியத்திற்கு சிறந்தது.'; + String get jiosaavn_source_description => + 'தெற்காசியப் பிராந்தியத்திற்கு சிறந்தது.'; @override - String get invidious_source_description => 'Piped ஐப் போன்றது ஆனால் அதிக கிடைக்கும் தன்மையுடன்.'; + String get invidious_source_description => + 'Piped ஐப் போன்றது ஆனால் அதிக கிடைக்கும் தன்மையுடன்.'; @override String highest_quality(Object quality) { @@ -967,13 +999,15 @@ class AppLocalizationsTa extends AppLocalizations { String get select_audio_source => 'ஒலி மூலத்தைத் தேர்ந்தெடுக்கவும்'; @override - String get endless_playback_description => 'வரிசையின் இறுதியில் புதிய பாடல்களை\nதானாகவே சேர்க்கவும்'; + String get endless_playback_description => + 'வரிசையின் இறுதியில் புதிய பாடல்களை\nதானாகவே சேர்க்கவும்'; @override String get choose_your_region => 'உங்கள் பிராந்தியத்தைத் தேர்ந்தெடுக்கவும்'; @override - String get choose_your_region_description => 'இது உங்கள் இருப்பிடத்திற்கான சரியான உள்ளடக்கத்தை\nSpotube காட்ட உதவும்.'; + String get choose_your_region_description => + 'இது உங்கள் இருப்பிடத்திற்கான சரியான உள்ளடக்கத்தை\nSpotube காட்ட உதவும்.'; @override String get choose_your_language => 'உங்கள் மொழியைத் தேர்ந்தெடுக்கவும்'; @@ -982,13 +1016,15 @@ class AppLocalizationsTa extends AppLocalizations { String get help_project_grow => 'இந்த திட்டம் வளர உதவுங்கள்'; @override - String get help_project_grow_description => 'Spotube ஒரு திறந்த மூல திட்டம். திட்டத்திற்கு பங்களிப்பு செய்வதன் மூலம், பிழைகளைப் புகாரளிப்பதன் மூலம் அல்லது புதிய அம்சங்களைப் பரிந்துரைப்பதன் மூலம் இந்தத் திட்டம் வளர உதவலாம்.'; + String get help_project_grow_description => + 'Spotube ஒரு திறந்த மூல திட்டம். திட்டத்திற்கு பங்களிப்பு செய்வதன் மூலம், பிழைகளைப் புகாரளிப்பதன் மூலம் அல்லது புதிய அம்சங்களைப் பரிந்துரைப்பதன் மூலம் இந்தத் திட்டம் வளர உதவலாம்.'; @override String get contribute_on_github => 'GitHub இல் பங்களியுங்கள்'; @override - String get donate_on_open_collective => 'Open Collective இல் நன்கொடை அளியுங்கள்'; + String get donate_on_open_collective => + 'Open Collective இல் நன்கொடை அளியுங்கள்'; @override String get browse_anonymously => 'அநாமதேயமாக உலாவுக'; @@ -997,7 +1033,8 @@ class AppLocalizationsTa extends AppLocalizations { String get enable_connect => 'இணைப்பை இயக்கு'; @override - String get enable_connect_description => 'மற்ற சாதனங்களிலிருந்து Spotube ஐக் கட்டுப்படுத்தவும்'; + String get enable_connect_description => + 'மற்ற சாதனங்களிலிருந்து Spotube ஐக் கட்டுப்படுத்தவும்'; @override String get devices => 'சாதனங்கள்'; @@ -1076,7 +1113,8 @@ class AppLocalizationsTa extends AppLocalizations { String get choose_the_device => 'சாதனத்தைத் தேர்ந்தெடுக்கவும்:'; @override - String get multiple_device_connected => 'பல சாதனங்கள் இணைக்கப்பட்டுள்ளன.\nஇந்த செயல் நடைபெற வேண்டிய சாதனத்தைத் தேர்ந்தெடுக்கவும்'; + String get multiple_device_connected => + 'பல சாதனங்கள் இணைக்கப்பட்டுள்ளன.\nஇந்த செயல் நடைபெற வேண்டிய சாதனத்தைத் தேர்ந்தெடுக்கவும்'; @override String get nothing_found => 'எதுவும் கிடைக்கவில்லை'; @@ -1171,7 +1209,8 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1259,8 @@ class AppLocalizationsTa extends AppLocalizations { String get webview_not_found => 'வெப்வியூ கிடைக்கவில்லை'; @override - String get webview_not_found_description => 'உங்கள் சாதனத்தில் எந்தவொரு வெப்வியூ இயக்கத்தை நிறுவவில்லை.\nஇது நிறுவப்பட்டிருந்தால், சுற்றுச்சூழல் பாதையில் PATH உள்ளது என்பதை உறுதிபடுத்தவும்\n\nநிறுவித்த பிறகு, செயலியை மறுதொடக்கம் செய்யவும்'; + String get webview_not_found_description => + 'உங்கள் சாதனத்தில் எந்தவொரு வெப்வியூ இயக்கத்தை நிறுவவில்லை.\nஇது நிறுவப்பட்டிருந்தால், சுற்றுச்சூழல் பாதையில் PATH உள்ளது என்பதை உறுதிபடுத்தவும்\n\nநிறுவித்த பிறகு, செயலியை மறுதொடக்கம் செய்யவும்'; @override String get unsupported_platform => 'அதிர்ஷ்டகாத உருப்படியை ஆதரிக்கவில்லை'; @@ -1252,7 +1292,8 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get export_cache_confirmation => 'இந்த கோப்புகளை ஏற்றுமதி செய்ய விரும்புகிறீர்களா?'; + String get export_cache_confirmation => + 'இந்த கோப்புகளை ஏற்றுமதி செய்ய விரும்புகிறீர்களா?'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,7 +1322,8 @@ class AppLocalizationsTa extends AppLocalizations { String get view_all => 'அனைத்தையும் காண்க'; @override - String get no_tracks_added_yet => 'உங்கள் பாடல்களை இன்னும் சேர்க்கவில்லை என்றால் தெரியாதே'; + String get no_tracks_added_yet => + 'உங்கள் பாடல்களை இன்னும் சேர்க்கவில்லை என்றால் தெரியாதே'; @override String get no_tracks => 'இங்கு பாடல்கள் எதுவும் இல்லை'; @@ -1293,7 +1335,8 @@ class AppLocalizationsTa extends AppLocalizations { String get not_following_artists => 'நீங்கள் எந்த கலைஞரையும் பின்தொடரவில்லை'; @override - String get no_favorite_albums_yet => 'நீங்கள் இன்னும் எந்த ஆல்பங்களையும் பிடித்தவையாகச் சேர்க்கவில்லை'; + String get no_favorite_albums_yet => + 'நீங்கள் இன்னும் எந்த ஆல்பங்களையும் பிடித்தவையாகச் சேர்க்கவில்லை'; @override String get no_logs_found => 'பதிவுகள் எதுவும் கிடைக்கவில்லை'; @@ -1317,7 +1360,8 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/unix போல் OS இல், .zshrc/.bashrc/.bash_profile போன்றவை அமைப்பில் பாதையை PATH அமைப்பது இயலாது.\nநீங்கள்.shell configuration file இல் பாதையை அமைக்க வேண்டும்'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/unix போல் OS இல், .zshrc/.bashrc/.bash_profile போன்றவை அமைப்பில் பாதையை PATH அமைப்பது இயலாது.\nநீங்கள்.shell configuration file இல் பாதையை அமைக்க வேண்டும்'; @override String get download => 'பதிவிறக்கு'; @@ -1335,7 +1379,8 @@ class AppLocalizationsTa extends AppLocalizations { String get edit_port => 'போர்டு திருத்தவும்'; @override - String get port_helper_msg => 'இயல்புநிலை -1 ஆகும், இது சீரற்ற எண்ணை குறிக்கிறது. நீங்கள் தீயணைப்பு அமைக்கப்பட்டிருந்தால், இதை அமைப்பது பரிந்துரைக்கப்படுகிறது.'; + String get port_helper_msg => + 'இயல்புநிலை -1 ஆகும், இது சீரற்ற எண்ணை குறிக்கிறது. நீங்கள் தீயணைப்பு அமைக்கப்பட்டிருந்தால், இதை அமைப்பது பரிந்துரைக்கப்படுகிறது.'; @override String connect_request(Object client) { @@ -1343,5 +1388,6 @@ class AppLocalizationsTa extends AppLocalizations { } @override - String get connection_request_denied => 'இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்.'; + String get connection_request_denied => + 'இணைப்பு மறுக்கப்பட்டது. பயனர் அணுகலை மறுத்தார்.'; } diff --git a/lib/l10n/generated/app_localizations_th.dart b/lib/l10n/generated/app_localizations_th.dart index 6fba4548..69c08b0d 100644 --- a/lib/l10n/generated/app_localizations_th.dart +++ b/lib/l10n/generated/app_localizations_th.dart @@ -407,7 +407,8 @@ class AppLocalizationsTh extends AppLocalizations { String get layout_mode => 'โหมดเค้าโครง'; @override - String get override_layout_settings => 'แทนที่การตั้งค่าโหมดเค้าโครงแบบตอบสนอง'; + String get override_layout_settings => + 'แทนที่การตั้งค่าโหมดเค้าโครงแบบตอบสนอง'; @override String get adaptive => 'ปรับเปลี่ยน'; @@ -437,7 +438,8 @@ class AppLocalizationsTh extends AppLocalizations { String get sync_album_color => 'ซิงค์สีอัลบั้ม'; @override - String get sync_album_color_description => 'ใช้สีเด่นของอาร์ตอัลบั้มเป็นสีเน้น'; + String get sync_album_color_description => + 'ใช้สีเด่นของอาร์ตอัลบั้มเป็นสีเน้น'; @override String get playback => 'การเล่น'; @@ -455,7 +457,8 @@ class AppLocalizationsTh extends AppLocalizations { String get pre_download_play => 'ดาวน์โหลดล่วงหน้าและเล่น'; @override - String get pre_download_play_description => 'แทนที่จะสตรีมเสียง ดาวน์โหลดข้อมูลและเล่นแทน (แนะนำสำหรับผู้ใช้แบนด์วิดธ์สูง)'; + String get pre_download_play_description => + 'แทนที่จะสตรีมเสียง ดาวน์โหลดข้อมูลและเล่นแทน (แนะนำสำหรับผู้ใช้แบนด์วิดธ์สูง)'; @override String get skip_non_music => 'ข้ามส่วนที่ไม่ใช่เพลง (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsTh extends AppLocalizations { String get blacklist_description => 'แทร็กและศิลปินที่บล็อก'; @override - String get wait_for_download_to_finish => 'โปรดรอให้การดาวน์โหลดปัจจุบันเสร็จสิ้น'; + String get wait_for_download_to_finish => + 'โปรดรอให้การดาวน์โหลดปัจจุบันเสร็จสิ้น'; @override String get desktop => 'เดสก์ท็อป'; @@ -500,7 +504,8 @@ class AppLocalizationsTh extends AppLocalizations { String get please_sponsor => 'กรุณาสนับสนุน/บริจาค'; @override - String get spotube_description => 'Spotube โปรแกรมเล่น Spotify ฟรีสำหรับทุกคน น้ำหนักเบา รองรับหลายแพลตฟอร์ม'; + String get spotube_description => + 'Spotube โปรแกรมเล่น Spotify ฟรีสำหรับทุกคน น้ำหนักเบา รองรับหลายแพลตฟอร์ม'; @override String get version => 'รุ่น'; @@ -532,7 +537,8 @@ class AppLocalizationsTh extends AppLocalizations { String get license => 'ใบอนุญาต'; @override - String get credentials_will_not_be_shared_disclaimer => 'ไม่ต้องกังวล ข้อมูลรับรองใดๆ ของคุณจะไม่ถูกเก็บรวบรวมหรือแชร์กับใคร'; + String get credentials_will_not_be_shared_disclaimer => + 'ไม่ต้องกังวล ข้อมูลรับรองใดๆ ของคุณจะไม่ถูกเก็บรวบรวมหรือแชร์กับใคร'; @override String get know_how_to_login => 'ไม่รู้จักวิธีดำเนินการนี้ใช่ไหม'; @@ -576,19 +582,23 @@ class AppLocalizationsTh extends AppLocalizations { String get piped_instance => 'อินสแตนซ์เซิร์ฟเวอร์แบบ Pipe'; @override - String get piped_description => 'อินสแตนซ์เซิร์ฟเวอร์แบบ Pipe ที่ใช้สำหรับการจับคู่แทร็ก'; + String get piped_description => + 'อินสแตนซ์เซิร์ฟเวอร์แบบ Pipe ที่ใช้สำหรับการจับคู่แทร็ก'; @override - String get piped_warning => 'บางอย่างอาจใช้งานไม่ได้ผล คุณจึงต้องรับความเสี่ยงเอง'; + String get piped_warning => + 'บางอย่างอาจใช้งานไม่ได้ผล คุณจึงต้องรับความเสี่ยงเอง'; @override String get invidious_instance => 'อินสแตนซ์เซิร์ฟเวอร์ Invidious'; @override - String get invidious_description => 'อินสแตนซ์เซิร์ฟเวอร์ Invidious ที่ใช้สำหรับการจับคู่เพลง'; + String get invidious_description => + 'อินสแตนซ์เซิร์ฟเวอร์ Invidious ที่ใช้สำหรับการจับคู่เพลง'; @override - String get invidious_warning => 'บางอันอาจใช้งานไม่ดี ใช้ด้วยความเสี่ยงของคุณเอง'; + String get invidious_warning => + 'บางอันอาจใช้งานไม่ดี ใช้ด้วยความเสี่ยงของคุณเอง'; @override String get generate => 'สร้าง'; @@ -711,22 +721,28 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get download_warning => 'ถ้าคุณดาวน์โหลดเพลงทั้งหมดเป็นจำนวนมาก คุณกำลังละเมิดลิขสิทธิ์เพลงและสร้างความเสียหายให้กับสังคมดนตรี สร้างสรรค์ หวังว่าคุณจะรับรู้เรื่องนี้ เสมอ พยายามเคารพและสนับสนุนผลงานหนักของศิลปิน'; + String get download_warning => + 'ถ้าคุณดาวน์โหลดเพลงทั้งหมดเป็นจำนวนมาก คุณกำลังละเมิดลิขสิทธิ์เพลงและสร้างความเสียหายให้กับสังคมดนตรี สร้างสรรค์ หวังว่าคุณจะรับรู้เรื่องนี้ เสมอ พยายามเคารพและสนับสนุนผลงานหนักของศิลปิน'; @override - String get download_ip_ban_warning => 'นอกเหนือจากนั้น IP ของคุณอาจถูกบล็อกบน YouTube เนื่องจากคำขอดาวน์โหลดมากเกินกว่าปกติ การบล็อก IP หมายความว่าคุณไม่สามารถใช้ YouTube (แม้ว่าคุณจะล็อกอินอยู่) เป็นเวลาอย่างน้อย 2-3 เดือนจากอุปกรณ์ IP นั้น และ Spotube จะไม่รับผิดชอบใด ๆ หากสิ่งนี้เกิดขึ้น'; + String get download_ip_ban_warning => + 'นอกเหนือจากนั้น IP ของคุณอาจถูกบล็อกบน YouTube เนื่องจากคำขอดาวน์โหลดมากเกินกว่าปกติ การบล็อก IP หมายความว่าคุณไม่สามารถใช้ YouTube (แม้ว่าคุณจะล็อกอินอยู่) เป็นเวลาอย่างน้อย 2-3 เดือนจากอุปกรณ์ IP นั้น และ Spotube จะไม่รับผิดชอบใด ๆ หากสิ่งนี้เกิดขึ้น'; @override - String get by_clicking_accept_terms => 'คลิก \'ยอมรับ\' คุณยินยอมตามเงื่อนไขต่อไปนี้:'; + String get by_clicking_accept_terms => + 'คลิก \'ยอมรับ\' คุณยินยอมตามเงื่อนไขต่อไปนี้:'; @override - String get download_agreement_1 => 'ฉันรู้ว่าฉันกำลังละเมิดลิขสิทธิ์เพลง ฉันเลว'; + String get download_agreement_1 => + 'ฉันรู้ว่าฉันกำลังละเมิดลิขสิทธิ์เพลง ฉันเลว'; @override - String get download_agreement_2 => 'ฉันจะสนับสนุนศิลปินทุกที่ที่ฉันทำได้และฉันทำสิ่งนี้เพียงเพราะฉันไม่มีเงินซื้อผลงานศิลปะของพวกเขา'; + String get download_agreement_2 => + 'ฉันจะสนับสนุนศิลปินทุกที่ที่ฉันทำได้และฉันทำสิ่งนี้เพียงเพราะฉันไม่มีเงินซื้อผลงานศิลปะของพวกเขา'; @override - String get download_agreement_3 => 'ฉันรับทราบอย่างสมบูรณ์ว่า IP ของฉันอาจถูกบล็อกบน YouTube และฉันจะไม่ถือ Spotube หรือเจ้าของ/ผู้มีส่วนร่วมใด ๆ รับผิดชอบต่ออุบัติเหตุใด ๆ ที่เกิดจากการกระทำปัจจุบันของฉัน'; + String get download_agreement_3 => + 'ฉันรับทราบอย่างสมบูรณ์ว่า IP ของฉันอาจถูกบล็อกบน YouTube และฉันจะไม่ถือ Spotube หรือเจ้าของ/ผู้มีส่วนร่วมใด ๆ รับผิดชอบต่ออุบัติเหตุใด ๆ ที่เกิดจากการกระทำปัจจุบันของฉัน'; @override String get decline => 'ปฏิเสธ'; @@ -807,7 +823,8 @@ class AppLocalizationsTh extends AppLocalizations { String get failed_to_encrypt => 'เข้ารหัสล้มเหลว'; @override - String get encryption_failed_warning => 'Spotube ใช้การเข้ารหัสเพื่อเก็บข้อมูลของคุณอย่างปลอดภัย แต่ไม่สามารถทำได้ ดังนั้นจะเปลี่ยนเป็นการจัดเก็บที่ไม่ปลอดภัย\nหากคุณใช้ Linux โปรดตรวจสอบว่าคุณได้ติดตั้งบริการลับ (gnome-keyring, kde-wallet, keepassxc เป็นต้น)'; + String get encryption_failed_warning => + 'Spotube ใช้การเข้ารหัสเพื่อเก็บข้อมูลของคุณอย่างปลอดภัย แต่ไม่สามารถทำได้ ดังนั้นจะเปลี่ยนเป็นการจัดเก็บที่ไม่ปลอดภัย\nหากคุณใช้ Linux โปรดตรวจสอบว่าคุณได้ติดตั้งบริการลับ (gnome-keyring, kde-wallet, keepassxc เป็นต้น)'; @override String get querying_info => 'กำลังดึงข้อมูล...'; @@ -824,7 +841,8 @@ class AppLocalizationsTh extends AppLocalizations { String get you_are_offline => 'คุณออฟไลน์อยู่'; @override - String get connection_restored => 'การเชื่อมต่ออินเทอร์เน็ตของคุณได้รับการกู้คืน'; + String get connection_restored => + 'การเชื่อมต่ออินเทอร์เน็ตของคุณได้รับการกู้คืน'; @override String get use_system_title_bar => 'ใช้แถบชื่อระบบ'; @@ -914,7 +932,8 @@ class AppLocalizationsTh extends AppLocalizations { String get how_to_start_radio => 'หากต้องการเปิดวิทยุฟังยังไง?'; @override - String get replace_queue_question => 'คุณต้องการแทนที่คิวปัจจุบันหรือเพิ่มเข้าไปหรือไม่'; + String get replace_queue_question => + 'คุณต้องการแทนที่คิวปัจจุบันหรือเพิ่มเข้าไปหรือไม่'; @override String get endless_playback => 'เล่นซ้ำ'; @@ -923,7 +942,8 @@ class AppLocalizationsTh extends AppLocalizations { String get delete_playlist => 'ลบเพลย์ลิสต์'; @override - String get delete_playlist_confirmation => 'คุณแน่ใจที่จะลบเพลย์ลิสต์นี้หรือไม่'; + String get delete_playlist_confirmation => + 'คุณแน่ใจที่จะลบเพลย์ลิสต์นี้หรือไม่'; @override String get local_tracks => 'เพลงในเครื่อง'; @@ -950,13 +970,15 @@ class AppLocalizationsTh extends AppLocalizations { String get youtube_source_description => 'แนะนำและใช้งานได้ดีที่สุด'; @override - String get piped_source_description => 'รู้สึกอิสระ? เหมือน YouTube แต่ฟรีกว่าเยอะ'; + String get piped_source_description => + 'รู้สึกอิสระ? เหมือน YouTube แต่ฟรีกว่าเยอะ'; @override String get jiosaavn_source_description => 'ดีที่สุดสำหรับภูมิภาคเอเชียใต้'; @override - String get invidious_source_description => 'คล้ายกับ Piped แต่มีความพร้อมใช้งานสูงกว่า'; + String get invidious_source_description => + 'คล้ายกับ Piped แต่มีความพร้อมใช้งานสูงกว่า'; @override String highest_quality(Object quality) { @@ -973,7 +995,8 @@ class AppLocalizationsTh extends AppLocalizations { String get choose_your_region => 'เลือกภูมิภาคของคุณ'; @override - String get choose_your_region_description => 'สิ่งนี้จะช่วยให้ Spotube แสดงเนื้อหาที่เหมาะสมสำหรับคุณ'; + String get choose_your_region_description => + 'สิ่งนี้จะช่วยให้ Spotube แสดงเนื้อหาที่เหมาะสมสำหรับคุณ'; @override String get choose_your_language => 'เลือกภาษาของคุณ'; @@ -982,7 +1005,8 @@ class AppLocalizationsTh extends AppLocalizations { String get help_project_grow => 'ช่วยให้โครงการนี้เติบโต'; @override - String get help_project_grow_description => 'Spotube เป็นโครงการโอเพนซอร์ส คุณสามารถช่วยให้โครงการนี้เติบโตได้โดยการมีส่วนร่วมในโครงการ รายงานข้อบกพร่อง หรือเสนอคุณสมบัติใหม่'; + String get help_project_grow_description => + 'Spotube เป็นโครงการโอเพนซอร์ส คุณสามารถช่วยให้โครงการนี้เติบโตได้โดยการมีส่วนร่วมในโครงการ รายงานข้อบกพร่อง หรือเสนอคุณสมบัติใหม่'; @override String get contribute_on_github => 'มีส่วนร่วมบน GitHub'; @@ -1076,7 +1100,8 @@ class AppLocalizationsTh extends AppLocalizations { String get choose_the_device => 'เลือกอุปกรณ์:'; @override - String get multiple_device_connected => 'มีอุปกรณ์เชื่อมต่อหลายเครื่อง\nเลือกอุปกรณ์ที่คุณต้องการให้การดำเนินการนี้เกิดขึ้น'; + String get multiple_device_connected => + 'มีอุปกรณ์เชื่อมต่อหลายเครื่อง\nเลือกอุปกรณ์ที่คุณต้องการให้การดำเนินการนี้เกิดขึ้น'; @override String get nothing_found => 'ไม่พบข้อมูล'; @@ -1149,7 +1174,8 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*คำนวณจากการจ่ายเงินต่อการสตรีมของ Spotify\nระหว่าง \$0.003 ถึง \$0.005 นี่เป็นการคำนวณสมมุติ\nเพื่อให้ข้อมูลแก่ผู้ใช้เกี่ยวกับจำนวนเงินที่พวกเขา\nอาจจะจ่ายให้กับศิลปินหากพวกเขาฟังเพลงของพวกเขาใน Spotify'; + String get streaming_fees_hypothetical => + '*คำนวณจากการจ่ายเงินต่อการสตรีมของ Spotify\nระหว่าง \$0.003 ถึง \$0.005 นี่เป็นการคำนวณสมมุติ\nเพื่อให้ข้อมูลแก่ผู้ใช้เกี่ยวกับจำนวนเงินที่พวกเขา\nอาจจะจ่ายให้กับศิลปินหากพวกเขาฟังเพลงของพวกเขาใน Spotify'; @override String get minutes_listened => 'เวลาที่ฟัง'; @@ -1171,7 +1197,8 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1247,8 @@ class AppLocalizationsTh extends AppLocalizations { String get webview_not_found => 'ไม่พบ Webview'; @override - String get webview_not_found_description => 'ไม่พบ runtime ของ Webview บนอุปกรณ์ของคุณ\nหากติดตั้งแล้วตรวจสอบให้แน่ใจว่าอยู่ใน environment PATH\n\nหลังจากติดตั้งแล้ว ให้รีสตาร์ทแอป'; + String get webview_not_found_description => + 'ไม่พบ runtime ของ Webview บนอุปกรณ์ของคุณ\nหากติดตั้งแล้วตรวจสอบให้แน่ใจว่าอยู่ใน environment PATH\n\nหลังจากติดตั้งแล้ว ให้รีสตาร์ทแอป'; @override String get unsupported_platform => 'แพลตฟอร์มไม่รองรับ'; @@ -1293,7 +1321,8 @@ class AppLocalizationsTh extends AppLocalizations { String get not_following_artists => 'คุณไม่ได้ติดตามศิลปินใด ๆ'; @override - String get no_favorite_albums_yet => 'ดูเหมือนคุณยังไม่ได้เพิ่มอัลบัมใด ๆ ในรายการโปรด'; + String get no_favorite_albums_yet => + 'ดูเหมือนคุณยังไม่ได้เพิ่มอัลบัมใด ๆ ในรายการโปรด'; @override String get no_logs_found => 'ไม่พบบันทึก'; @@ -1317,7 +1346,8 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'ใน macOS/Linux/Unix อย่าง OS การตั้งค่าพาธใน .zshrc/.bashrc/.bash_profile เป็นต้น จะไม่ทำงาน\nคุณต้องตั้งค่าพาธในไฟล์การกำหนดค่า shell'; + String get youtube_engine_unix_issue_message => + 'ใน macOS/Linux/Unix อย่าง OS การตั้งค่าพาธใน .zshrc/.bashrc/.bash_profile เป็นต้น จะไม่ทำงาน\nคุณต้องตั้งค่าพาธในไฟล์การกำหนดค่า shell'; @override String get download => 'ดาวน์โหลด'; @@ -1335,7 +1365,8 @@ class AppLocalizationsTh extends AppLocalizations { String get edit_port => 'แก้ไขพอร์ต'; @override - String get port_helper_msg => 'ค่าเริ่มต้นคือ -1 ซึ่งหมายถึงหมายเลขสุ่ม หากคุณได้กำหนดค่าไฟร์วอลล์แล้ว แนะนำให้ตั้งค่านี้'; + String get port_helper_msg => + 'ค่าเริ่มต้นคือ -1 ซึ่งหมายถึงหมายเลขสุ่ม หากคุณได้กำหนดค่าไฟร์วอลล์แล้ว แนะนำให้ตั้งค่านี้'; @override String connect_request(Object client) { @@ -1343,5 +1374,6 @@ class AppLocalizationsTh extends AppLocalizations { } @override - String get connection_request_denied => 'การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง'; + String get connection_request_denied => + 'การเชื่อมต่อล้มเหลว ผู้ใช้ปฏิเสธการเข้าถึง'; } diff --git a/lib/l10n/generated/app_localizations_tl.dart b/lib/l10n/generated/app_localizations_tl.dart index 6bbcd8cb..3c4de958 100644 --- a/lib/l10n/generated/app_localizations_tl.dart +++ b/lib/l10n/generated/app_localizations_tl.dart @@ -79,7 +79,8 @@ class AppLocalizationsTl extends AppLocalizations { String get liked_tracks => 'Mga Nagustuhang Track'; @override - String get liked_tracks_description => 'Lahat ng mga track na iyong nagustuhan'; + String get liked_tracks_description => + 'Lahat ng mga track na iyong nagustuhan'; @override String get playlist => 'Playlist'; @@ -342,7 +343,8 @@ class AppLocalizationsTl extends AppLocalizations { String get queue => 'Pila'; @override - String get alternative_track_sources => 'Alternatibong mga pinagmulan ng track'; + String get alternative_track_sources => + 'Alternatibong mga pinagmulan ng track'; @override String get download_track => 'I-download ang track'; @@ -407,7 +409,8 @@ class AppLocalizationsTl extends AppLocalizations { String get layout_mode => 'Mode ng Layout'; @override - String get override_layout_settings => 'I-override ang mga setting ng responsive layout mode'; + String get override_layout_settings => + 'I-override ang mga setting ng responsive layout mode'; @override String get adaptive => 'Umaangkop'; @@ -437,7 +440,8 @@ class AppLocalizationsTl extends AppLocalizations { String get sync_album_color => 'I-sync ang kulay ng album'; @override - String get sync_album_color_description => 'Ginagamit ang pangunahing kulay ng album art bilang kulay ng accent'; + String get sync_album_color_description => + 'Ginagamit ang pangunahing kulay ng album art bilang kulay ng accent'; @override String get playback => 'Playback'; @@ -455,16 +459,19 @@ class AppLocalizationsTl extends AppLocalizations { String get pre_download_play => 'Mag-pre-download at i-play'; @override - String get pre_download_play_description => 'Sa halip na mag-stream ng audio, mag-download ng bytes at i-play sa halip (Inirerekomenda para sa mga gumagamit ng mataas na bandwidth)'; + String get pre_download_play_description => + 'Sa halip na mag-stream ng audio, mag-download ng bytes at i-play sa halip (Inirerekomenda para sa mga gumagamit ng mataas na bandwidth)'; @override - String get skip_non_music => 'Laktawan ang mga segment na hindi musika (SponsorBlock)'; + String get skip_non_music => + 'Laktawan ang mga segment na hindi musika (SponsorBlock)'; @override String get blacklist_description => 'Mga track at artista na nasa blacklist'; @override - String get wait_for_download_to_finish => 'Mangyaring maghintay para matapos ang kasalukuyang pag-download'; + String get wait_for_download_to_finish => + 'Mangyaring maghintay para matapos ang kasalukuyang pag-download'; @override String get desktop => 'Desktop'; @@ -500,7 +507,8 @@ class AppLocalizationsTl extends AppLocalizations { String get please_sponsor => 'Mangyaring Mag-sponsor/Mag-donate'; @override - String get spotube_description => 'Spotube, isang magaan, cross-platform, libreng-para-sa-lahat na spotify client'; + String get spotube_description => + 'Spotube, isang magaan, cross-platform, libreng-para-sa-lahat na spotify client'; @override String get version => 'Bersyon'; @@ -532,7 +540,8 @@ class AppLocalizationsTl extends AppLocalizations { String get license => 'Lisensya'; @override - String get credentials_will_not_be_shared_disclaimer => 'Huwag mag-alala, ang alinman sa iyong mga kredensyal ay hindi kokolektahin o ibabahagi sa sinuman'; + String get credentials_will_not_be_shared_disclaimer => + 'Huwag mag-alala, ang alinman sa iyong mga kredensyal ay hindi kokolektahin o ibabahagi sa sinuman'; @override String get know_how_to_login => 'Hindi mo alam kung paano gawin ito?'; @@ -576,19 +585,23 @@ class AppLocalizationsTl extends AppLocalizations { String get piped_instance => 'Instance ng Piped Server'; @override - String get piped_description => 'Ang instance ng Piped server na gagamitin para sa pagtutugma ng track'; + String get piped_description => + 'Ang instance ng Piped server na gagamitin para sa pagtutugma ng track'; @override - String get piped_warning => 'Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro'; + String get piped_warning => + 'Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro'; @override String get invidious_instance => 'Instance ng Invidious Server'; @override - String get invidious_description => 'Ang instance ng Invidious server na gagamitin para sa pagtutugma ng track'; + String get invidious_description => + 'Ang instance ng Invidious server na gagamitin para sa pagtutugma ng track'; @override - String get invidious_warning => 'Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro'; + String get invidious_warning => + 'Maaaring hindi gumagana nang mabuti ang ilan sa mga ito. Kaya gamitin sa sarili mong peligro'; @override String get generate => 'Gumawa'; @@ -599,13 +612,16 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get replace_downloaded_tracks => 'Palitan ang lahat ng na-download na mga track'; + String get replace_downloaded_tracks => + 'Palitan ang lahat ng na-download na mga track'; @override - String get skip_download_tracks => 'Laktawan ang pag-download ng lahat ng na-download na mga track'; + String get skip_download_tracks => + 'Laktawan ang pag-download ng lahat ng na-download na mga track'; @override - String get do_you_want_to_replace => 'Gusto mo bang palitan ang umiiral na track??'; + String get do_you_want_to_replace => + 'Gusto mo bang palitan ang umiiral na track??'; @override String get replace => 'Palitan'; @@ -711,22 +727,28 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get download_warning => 'Kung nag-download ka ng lahat ng Track sa maramihan, malinaw na nagpa-pirate ka ng Musika at nagsasanhi ng pinsala sa creative society ng Musika. Sana ay alam mo ito. Palaging, subukang igalang at suportahan ang masipag na paggawa ng Artist'; + String get download_warning => + 'Kung nag-download ka ng lahat ng Track sa maramihan, malinaw na nagpa-pirate ka ng Musika at nagsasanhi ng pinsala sa creative society ng Musika. Sana ay alam mo ito. Palaging, subukang igalang at suportahan ang masipag na paggawa ng Artist'; @override - String get download_ip_ban_warning => 'Sa nga pala, ang iyong IP ay maaaring ma-block sa YouTube dahil sa sobrang mga kahilingan sa pag-download kaysa sa karaniwan. Ang IP block ay nangangahulugang hindi mo magagamit ang YouTube (kahit na naka-log in ka) sa loob ng hindi bababa sa 2-3 buwan mula sa device na may IP na iyon. At hindi pinanghahawakan ng Spotube ang anumang responsibilidad kung mangyayari ito'; + String get download_ip_ban_warning => + 'Sa nga pala, ang iyong IP ay maaaring ma-block sa YouTube dahil sa sobrang mga kahilingan sa pag-download kaysa sa karaniwan. Ang IP block ay nangangahulugang hindi mo magagamit ang YouTube (kahit na naka-log in ka) sa loob ng hindi bababa sa 2-3 buwan mula sa device na may IP na iyon. At hindi pinanghahawakan ng Spotube ang anumang responsibilidad kung mangyayari ito'; @override - String get by_clicking_accept_terms => 'Sa pamamagitan ng pag-click sa \'tanggapin\', sumasang-ayon ka sa mga sumusunod na tuntunin:'; + String get by_clicking_accept_terms => + 'Sa pamamagitan ng pag-click sa \'tanggapin\', sumasang-ayon ka sa mga sumusunod na tuntunin:'; @override - String get download_agreement_1 => 'Alam kong nagpa-pirate ako ng Musika. Masama ako'; + String get download_agreement_1 => + 'Alam kong nagpa-pirate ako ng Musika. Masama ako'; @override - String get download_agreement_2 => 'Susuportahan ko ang Artist saan man ako maaari at ginagawa ko lang ito dahil wala akong pera para bumili ng kanilang sining'; + String get download_agreement_2 => + 'Susuportahan ko ang Artist saan man ako maaari at ginagawa ko lang ito dahil wala akong pera para bumili ng kanilang sining'; @override - String get download_agreement_3 => 'Lubos kong nauunawaan na ang aking IP ay maaaring ma-block sa YouTube at hindi ko pinanghahawakan ang Spotube o ang kanyang mga may-ari/nag-ambag na responsable para sa anumang aksidente na sanhi ng aking kasalukuyang aksyon'; + String get download_agreement_3 => + 'Lubos kong nauunawaan na ang aking IP ay maaaring ma-block sa YouTube at hindi ko pinanghahawakan ang Spotube o ang kanyang mga may-ari/nag-ambag na responsable para sa anumang aksidente na sanhi ng aking kasalukuyang aksyon'; @override String get decline => 'Tanggihan'; @@ -807,7 +829,8 @@ class AppLocalizationsTl extends AppLocalizations { String get failed_to_encrypt => 'Nabigong i-encrypt'; @override - String get encryption_failed_warning => 'Gumagamit ng encryption ang Spotube para ligtas na i-store ang iyong data. Ngunit nabigo. Kaya babalik ito sa hindi secure na storage\nKung gumagamit ka ng linux, mangyaring tiyakin na mayroon kang anumang secret-service na naka-install (gnome-keyring, kde-wallet, keepassxc atbp)'; + String get encryption_failed_warning => + 'Gumagamit ng encryption ang Spotube para ligtas na i-store ang iyong data. Ngunit nabigo. Kaya babalik ito sa hindi secure na storage\nKung gumagamit ka ng linux, mangyaring tiyakin na mayroon kang anumang secret-service na naka-install (gnome-keyring, kde-wallet, keepassxc atbp)'; @override String get querying_info => 'Kinukuha ang impormasyon...'; @@ -824,7 +847,8 @@ class AppLocalizationsTl extends AppLocalizations { String get you_are_offline => 'Kasalukuyan kang offline'; @override - String get connection_restored => 'Naibalik na ang iyong koneksyon sa internet'; + String get connection_restored => + 'Naibalik na ang iyong koneksyon sa internet'; @override String get use_system_title_bar => 'Gamitin ang title bar ng system'; @@ -881,7 +905,8 @@ class AppLocalizationsTl extends AppLocalizations { String get login => 'Mag-login'; @override - String get login_with_your_lastfm => 'Mag-login gamit ang iyong Last.fm account'; + String get login_with_your_lastfm => + 'Mag-login gamit ang iyong Last.fm account'; @override String get scrobble_to_lastfm => 'I-scrobble sa Last.fm'; @@ -905,7 +930,8 @@ class AppLocalizationsTl extends AppLocalizations { String get friends => 'Mga Kaibigan'; @override - String get no_lyrics_available => 'Paumanhin, hindi mahanap ang lyrics para sa track na ito'; + String get no_lyrics_available => + 'Paumanhin, hindi mahanap ang lyrics para sa track na ito'; @override String get start_a_radio => 'Magsimula ng Radio'; @@ -914,7 +940,8 @@ class AppLocalizationsTl extends AppLocalizations { String get how_to_start_radio => 'Paano mo gustong simulan ang radio?'; @override - String get replace_queue_question => 'Gusto mo bang palitan ang kasalukuyang pila o idagdag dito?'; + String get replace_queue_question => + 'Gusto mo bang palitan ang kasalukuyang pila o idagdag dito?'; @override String get endless_playback => 'Walang Hanggang Playback'; @@ -923,7 +950,8 @@ class AppLocalizationsTl extends AppLocalizations { String get delete_playlist => 'Burahin ang Playlist'; @override - String get delete_playlist_confirmation => 'Sigurado ka bang gusto mong burahin ang playlist na ito?'; + String get delete_playlist_confirmation => + 'Sigurado ka bang gusto mong burahin ang playlist na ito?'; @override String get local_tracks => 'Mga Lokal na Track'; @@ -947,16 +975,20 @@ class AppLocalizationsTl extends AppLocalizations { String get get_started => 'Magsimula na tayo'; @override - String get youtube_source_description => 'Inirerekomenda at pinakamahusay na gumagana.'; + String get youtube_source_description => + 'Inirerekomenda at pinakamahusay na gumagana.'; @override - String get piped_source_description => 'Gusto ng kalayaan? Kapareho ng YouTube ngunit mas malaya.'; + String get piped_source_description => + 'Gusto ng kalayaan? Kapareho ng YouTube ngunit mas malaya.'; @override - String get jiosaavn_source_description => 'Pinakamahusay para sa rehiyon ng South Asia.'; + String get jiosaavn_source_description => + 'Pinakamahusay para sa rehiyon ng South Asia.'; @override - String get invidious_source_description => 'Katulad ng Piped ngunit may mas mataas na availability.'; + String get invidious_source_description => + 'Katulad ng Piped ngunit may mas mataas na availability.'; @override String highest_quality(Object quality) { @@ -967,13 +999,15 @@ class AppLocalizationsTl extends AppLocalizations { String get select_audio_source => 'Pumili ng Pinagmulan ng Audio'; @override - String get endless_playback_description => 'Awtomatikong magdagdag ng mga bagong kanta\nsa dulo ng pila'; + String get endless_playback_description => + 'Awtomatikong magdagdag ng mga bagong kanta\nsa dulo ng pila'; @override String get choose_your_region => 'Piliin ang iyong rehiyon'; @override - String get choose_your_region_description => 'Ito ay tutulong sa Spotube na ipakita sa iyo ang tamang content\npara sa iyong lokasyon.'; + String get choose_your_region_description => + 'Ito ay tutulong sa Spotube na ipakita sa iyo ang tamang content\npara sa iyong lokasyon.'; @override String get choose_your_language => 'Piliin ang iyong wika'; @@ -982,7 +1016,8 @@ class AppLocalizationsTl extends AppLocalizations { String get help_project_grow => 'Tulungan ang proyektong ito na lumago'; @override - String get help_project_grow_description => 'Ang Spotube ay isang open-source na proyekto. Maaari mong tulungan ang proyektong ito na lumago sa pamamagitan ng pag-contribute sa proyekto, pag-ulat ng mga bug, o pagmungkahi ng mga bagong feature.'; + String get help_project_grow_description => + 'Ang Spotube ay isang open-source na proyekto. Maaari mong tulungan ang proyektong ito na lumago sa pamamagitan ng pag-contribute sa proyekto, pag-ulat ng mga bug, o pagmungkahi ng mga bagong feature.'; @override String get contribute_on_github => 'Mag-contribute sa GitHub'; @@ -997,7 +1032,8 @@ class AppLocalizationsTl extends AppLocalizations { String get enable_connect => 'I-enable ang Connect'; @override - String get enable_connect_description => 'Kontrolin ang Spotube mula sa ibang mga device'; + String get enable_connect_description => + 'Kontrolin ang Spotube mula sa ibang mga device'; @override String get devices => 'Mga Device'; @@ -1076,7 +1112,8 @@ class AppLocalizationsTl extends AppLocalizations { String get choose_the_device => 'Piliin ang device:'; @override - String get multiple_device_connected => 'Mayroong maraming device na nakakonekta.\nPiliin ang device kung saan mo gustong maganap ang aksyon na ito'; + String get multiple_device_connected => + 'Mayroong maraming device na nakakonekta.\nPiliin ang device kung saan mo gustong maganap ang aksyon na ito'; @override String get nothing_found => 'Walang nahanap'; @@ -1149,7 +1186,8 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get streaming_fees_hypothetical => 'Mga bayarin sa streaming (hypothetical)'; + String get streaming_fees_hypothetical => + 'Mga bayarin sa streaming (hypothetical)'; @override String get minutes_listened => 'Mga minutong pinapakinggan'; @@ -1171,7 +1209,8 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1259,8 @@ class AppLocalizationsTl extends AppLocalizations { String get webview_not_found => 'Hindi nahanap ang Webview'; @override - String get webview_not_found_description => 'Walang webview runtime na naka-install sa iyong device.\nKung naka-install ito, siguraduhing nasa Environment PATH\n\nPagkatapos mag-install, i-restart ang app'; + String get webview_not_found_description => + 'Walang webview runtime na naka-install sa iyong device.\nKung naka-install ito, siguraduhing nasa Environment PATH\n\nPagkatapos mag-install, i-restart ang app'; @override String get unsupported_platform => 'Hindi suportadong platform'; @@ -1252,7 +1292,8 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get export_cache_confirmation => 'Gusto mo bang i-export ang mga file na ito sa'; + String get export_cache_confirmation => + 'Gusto mo bang i-export ang mga file na ito sa'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,7 +1322,8 @@ class AppLocalizationsTl extends AppLocalizations { String get view_all => 'Tingnan lahat'; @override - String get no_tracks_added_yet => 'Mukhang wala ka pang idinaragdag na mga track'; + String get no_tracks_added_yet => + 'Mukhang wala ka pang idinaragdag na mga track'; @override String get no_tracks => 'Mukhang walang mga track dito'; @@ -1290,10 +1332,12 @@ class AppLocalizationsTl extends AppLocalizations { String get no_tracks_listened_yet => 'Mukhang wala ka pang pinakikinggan'; @override - String get not_following_artists => 'Hindi ka sumusunod sa anumang mga artista'; + String get not_following_artists => + 'Hindi ka sumusunod sa anumang mga artista'; @override - String get no_favorite_albums_yet => 'Mukhang wala ka pang idinagdag na anumang mga album sa iyong mga paborito'; + String get no_favorite_albums_yet => + 'Mukhang wala ka pang idinagdag na anumang mga album sa iyong mga paborito'; @override String get no_logs_found => 'Walang nahanap na mga log'; @@ -1317,7 +1361,8 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'Sa macOS/Linux/unix tulad ng OS, ang pag-set ng path sa .zshrc/.bashrc/.bash_profile atbp. ay hindi gagana.\nKailangan mong i-set ang path sa configuration file ng shell'; + String get youtube_engine_unix_issue_message => + 'Sa macOS/Linux/unix tulad ng OS, ang pag-set ng path sa .zshrc/.bashrc/.bash_profile atbp. ay hindi gagana.\nKailangan mong i-set ang path sa configuration file ng shell'; @override String get download => 'I-download'; @@ -1335,7 +1380,8 @@ class AppLocalizationsTl extends AppLocalizations { String get edit_port => 'I-edit ang port'; @override - String get port_helper_msg => 'Ang default ay -1 na nagpapahiwatig ng random na numero. Kung na-configure mo ang firewall, inirerekomenda na itakda ito.'; + String get port_helper_msg => + 'Ang default ay -1 na nagpapahiwatig ng random na numero. Kung na-configure mo ang firewall, inirerekomenda na itakda ito.'; @override String connect_request(Object client) { @@ -1343,5 +1389,6 @@ class AppLocalizationsTl extends AppLocalizations { } @override - String get connection_request_denied => 'Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access.'; + String get connection_request_denied => + 'Tanggihan ang koneksyon. Tinanggihan ng gumagamit ang pag-access.'; } diff --git a/lib/l10n/generated/app_localizations_tr.dart b/lib/l10n/generated/app_localizations_tr.dart index cc275ac3..93ec14fb 100644 --- a/lib/l10n/generated/app_localizations_tr.dart +++ b/lib/l10n/generated/app_localizations_tr.dart @@ -27,7 +27,8 @@ class AppLocalizationsTr extends AppLocalizations { String get settings => 'Ayarlar'; @override - String get genre_categories_filter => 'Kategorileri veya türleri filtreleyin...'; + String get genre_categories_filter => + 'Kategorileri veya türleri filtreleyin...'; @override String get genre => 'Tür'; @@ -356,7 +357,8 @@ class AppLocalizationsTr extends AppLocalizations { String get clear_all => 'Tümünü temizle'; @override - String get show_hide_ui_on_hover => 'Fareyle üzerine gelindiğinde kullanıcı arayüzünü göster/gizle'; + String get show_hide_ui_on_hover => + 'Fareyle üzerine gelindiğinde kullanıcı arayüzünü göster/gizle'; @override String get always_on_top => 'Her zaman üstte'; @@ -407,7 +409,8 @@ class AppLocalizationsTr extends AppLocalizations { String get layout_mode => 'Düzen modu'; @override - String get override_layout_settings => 'Duyarlı düzen modu ayarlarını geçersiz kıl'; + String get override_layout_settings => + 'Duyarlı düzen modu ayarlarını geçersiz kıl'; @override String get adaptive => 'Uyarlanabilir'; @@ -437,7 +440,8 @@ class AppLocalizationsTr extends AppLocalizations { String get sync_album_color => 'Albüm rengini senkronize et'; @override - String get sync_album_color_description => 'Vurgu rengi olarak albüm resminin baskın rengini kullanır'; + String get sync_album_color_description => + 'Vurgu rengi olarak albüm resminin baskın rengini kullanır'; @override String get playback => 'Oynatma'; @@ -455,16 +459,19 @@ class AppLocalizationsTr extends AppLocalizations { String get pre_download_play => 'Önceden indir ve oynat'; @override - String get pre_download_play_description => 'Ses akışı yerine baytları indir ve oynat (Daha yüksek bant genişliğine sahip kullanıcılar için önerilir)'; + String get pre_download_play_description => + 'Ses akışı yerine baytları indir ve oynat (Daha yüksek bant genişliğine sahip kullanıcılar için önerilir)'; @override String get skip_non_music => 'Müzik olmayan bölümleri atlat (SponsorBlock)'; @override - String get blacklist_description => 'Kara listeye alınan parçalar ve sanatçılar'; + String get blacklist_description => + 'Kara listeye alınan parçalar ve sanatçılar'; @override - String get wait_for_download_to_finish => 'Lütfen mevcut indirme işleminin tamamlanmasını bekleyin'; + String get wait_for_download_to_finish => + 'Lütfen mevcut indirme işleminin tamamlanmasını bekleyin'; @override String get desktop => 'Masaüstü'; @@ -500,7 +507,8 @@ class AppLocalizationsTr extends AppLocalizations { String get please_sponsor => 'Sponsor Ol/Bağış Yap'; @override - String get spotube_description => 'Spotube, hafif, platformlar arası uyumlu ve herkes için ücretsiz bir Spotify istemcisidir.'; + String get spotube_description => + 'Spotube, hafif, platformlar arası uyumlu ve herkes için ücretsiz bir Spotify istemcisidir.'; @override String get version => 'Sürüm'; @@ -532,7 +540,8 @@ class AppLocalizationsTr extends AppLocalizations { String get license => 'Lisans'; @override - String get credentials_will_not_be_shared_disclaimer => 'Endişelenmeyin, kimlik bilgilerinizden hiçbiri toplanmayacak veya kimseyle paylaşılmayacak'; + String get credentials_will_not_be_shared_disclaimer => + 'Endişelenmeyin, kimlik bilgilerinizden hiçbiri toplanmayacak veya kimseyle paylaşılmayacak'; @override String get know_how_to_login => 'Bunu nasıl yapacağınızı bilmiyor musunuz?'; @@ -576,19 +585,23 @@ class AppLocalizationsTr extends AppLocalizations { String get piped_instance => 'Piped sunucu örneği'; @override - String get piped_description => 'Parça eşleştirme için kullanılacak Piped sunucu örneği'; + String get piped_description => + 'Parça eşleştirme için kullanılacak Piped sunucu örneği'; @override - String get piped_warning => 'Bazıları iyi çalışmayabilir. Yani riski size ait olmak üzere kullanın'; + String get piped_warning => + 'Bazıları iyi çalışmayabilir. Yani riski size ait olmak üzere kullanın'; @override String get invidious_instance => 'Invidious Sunucu Örneği'; @override - String get invidious_description => 'Parça eşleştirmesi için kullanılacak Invidious sunucu örneği'; + String get invidious_description => + 'Parça eşleştirmesi için kullanılacak Invidious sunucu örneği'; @override - String get invidious_warning => 'Bazıları iyi çalışmayabilir. Kendi riskinizde kullanın'; + String get invidious_warning => + 'Bazıları iyi çalışmayabilir. Kendi riskinizde kullanın'; @override String get generate => 'Oluştur'; @@ -605,7 +618,8 @@ class AppLocalizationsTr extends AppLocalizations { String get skip_download_tracks => 'İndirilen tüm parçaları indirmeyi atla'; @override - String get do_you_want_to_replace => 'Mevcut parçayı değiştirmek istiyor musunuz?'; + String get do_you_want_to_replace => + 'Mevcut parçayı değiştirmek istiyor musunuz?'; @override String get replace => 'Değiştir'; @@ -711,22 +725,28 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get download_warning => 'Tüm şarkıları toplu olarak indiriyorsanız, açıkça müzik korsanlığı yapıyorsunuz ve müzik dünyasının yaratıcı topluluğuna zarar veriyorsunuz demektir. Umuyorum bunun farkındasınızdır. Her zaman, sanatçıların emeğine saygı göstermeyi ve desteklemeyi deneyin.'; + String get download_warning => + 'Tüm şarkıları toplu olarak indiriyorsanız, açıkça müzik korsanlığı yapıyorsunuz ve müzik dünyasının yaratıcı topluluğuna zarar veriyorsunuz demektir. Umuyorum bunun farkındasınızdır. Her zaman, sanatçıların emeğine saygı göstermeyi ve desteklemeyi deneyin.'; @override - String get download_ip_ban_warning => 'Ayrıca, normalden fazla indirme istekleri nedeniyle YouTube\'da IP\'niz engellenebilir. IP engeli, en az 2-3 ay boyunca YouTube\'u (hatta oturum açmış olsanız bile) o IP cihazından kullanamayacağınız anlamına gelir. Ve eğer böyle bir durum yaşanırsa, Spotube bundan hiçbir sorumluluk kabul etmez.'; + String get download_ip_ban_warning => + 'Ayrıca, normalden fazla indirme istekleri nedeniyle YouTube\'da IP\'niz engellenebilir. IP engeli, en az 2-3 ay boyunca YouTube\'u (hatta oturum açmış olsanız bile) o IP cihazından kullanamayacağınız anlamına gelir. Ve eğer böyle bir durum yaşanırsa, Spotube bundan hiçbir sorumluluk kabul etmez.'; @override - String get by_clicking_accept_terms => '\"Kabul et\" e tıklayarak aşağıdaki şartları kabul etmiş olursunuz:'; + String get by_clicking_accept_terms => + '\"Kabul et\" e tıklayarak aşağıdaki şartları kabul etmiş olursunuz:'; @override - String get download_agreement_1 => 'Müzik korsanlığı yaptığımı biliyorum. Ben fakir biriyim.'; + String get download_agreement_1 => + 'Müzik korsanlığı yaptığımı biliyorum. Ben fakir biriyim.'; @override - String get download_agreement_2 => 'Sanatçıyı elimden geldiğince destekleyeceğim ve bunu sadece sanatını satın alacak param olmadığı için yapıyorum'; + String get download_agreement_2 => + 'Sanatçıyı elimden geldiğince destekleyeceğim ve bunu sadece sanatını satın alacak param olmadığı için yapıyorum'; @override - String get download_agreement_3 => 'YouTube\'da IP\'min engellenebileceğinin tamamen farkındayım ve mevcut eylemlerimden kaynaklanan herhangi bir kaza için Spotube\'u veya sahiplerini/katkıda bulunanları sorumlu tutmuyorum.'; + String get download_agreement_3 => + 'YouTube\'da IP\'min engellenebileceğinin tamamen farkındayım ve mevcut eylemlerimden kaynaklanan herhangi bir kaza için Spotube\'u veya sahiplerini/katkıda bulunanları sorumlu tutmuyorum.'; @override String get decline => 'Reddet'; @@ -807,7 +827,8 @@ class AppLocalizationsTr extends AppLocalizations { String get failed_to_encrypt => 'Şifreleme başarısız oldu'; @override - String get encryption_failed_warning => 'Spotube, verilerinizi güvenli bir şekilde depolamak için şifreleme kullanır. Ancak bunu başaramadı. Bu nedenle, güvensiz depolamaya geri dönecektir\nLinux kullanıyorsanız, lütfen gnome-keyring, kde-wallet, keepassxc vb. herhangi bir gizli servisin yüklü olduğundan emin olun.'; + String get encryption_failed_warning => + 'Spotube, verilerinizi güvenli bir şekilde depolamak için şifreleme kullanır. Ancak bunu başaramadı. Bu nedenle, güvensiz depolamaya geri dönecektir\nLinux kullanıyorsanız, lütfen gnome-keyring, kde-wallet, keepassxc vb. herhangi bir gizli servisin yüklü olduğundan emin olun.'; @override String get querying_info => 'Bilgi sorgulanıyor...'; @@ -914,7 +935,8 @@ class AppLocalizationsTr extends AppLocalizations { String get how_to_start_radio => 'Radyoyu nasıl başlatmak istersiniz?'; @override - String get replace_queue_question => 'Mevcut kuyruğu değiştirmek mi yoksa eklemek mi istersiniz?'; + String get replace_queue_question => + 'Mevcut kuyruğu değiştirmek mi yoksa eklemek mi istersiniz?'; @override String get endless_playback => 'Sonsuz olarak oynat'; @@ -923,7 +945,8 @@ class AppLocalizationsTr extends AppLocalizations { String get delete_playlist => 'Oynatma listesini sil'; @override - String get delete_playlist_confirmation => 'Bu oynatma listesini silmek istediğinizden emin misiniz?'; + String get delete_playlist_confirmation => + 'Bu oynatma listesini silmek istediğinizden emin misiniz?'; @override String get local_tracks => 'Yerel parçalar'; @@ -947,16 +970,19 @@ class AppLocalizationsTr extends AppLocalizations { String get get_started => 'Haydi başlayalım'; @override - String get youtube_source_description => 'Tavsiye edilir ve en iyi şekilde çalışır.'; + String get youtube_source_description => + 'Tavsiye edilir ve en iyi şekilde çalışır.'; @override - String get piped_source_description => 'Özgür hissediyor musunuz? YouTube ile aynı, ama çok daha özgür.'; + String get piped_source_description => + 'Özgür hissediyor musunuz? YouTube ile aynı, ama çok daha özgür.'; @override String get jiosaavn_source_description => 'Güney Asya bölgesi için en iyisi.'; @override - String get invidious_source_description => 'Piped\'a benzer, ancak daha yüksek kullanılabilirliğe sahip.'; + String get invidious_source_description => + 'Piped\'a benzer, ancak daha yüksek kullanılabilirliğe sahip.'; @override String highest_quality(Object quality) { @@ -967,13 +993,15 @@ class AppLocalizationsTr extends AppLocalizations { String get select_audio_source => 'Ses kaynağını seçin'; @override - String get endless_playback_description => 'Yeni şarkıları otomatik olarak\nkuyruğun sonuna ekle'; + String get endless_playback_description => + 'Yeni şarkıları otomatik olarak\nkuyruğun sonuna ekle'; @override String get choose_your_region => 'Bölgenizi seçin'; @override - String get choose_your_region_description => 'Bu, Spotube\'un konumunuza uygun içerikleri göstermesine yardımcı olacaktır.'; + String get choose_your_region_description => + 'Bu, Spotube\'un konumunuza uygun içerikleri göstermesine yardımcı olacaktır.'; @override String get choose_your_language => 'Dilinizi seçin'; @@ -982,7 +1010,8 @@ class AppLocalizationsTr extends AppLocalizations { String get help_project_grow => 'Bu projenin büyümesine yardımcı olun'; @override - String get help_project_grow_description => 'Spotube açık kaynaklı bir projedir. Projeye katkıda bulunarak, hataları bildirerek veya yeni özellikler önererek bu projenin büyümesine yardımcı olabilirsiniz.'; + String get help_project_grow_description => + 'Spotube açık kaynaklı bir projedir. Projeye katkıda bulunarak, hataları bildirerek veya yeni özellikler önererek bu projenin büyümesine yardımcı olabilirsiniz.'; @override String get contribute_on_github => 'GitHub\'da katkıda bulun'; @@ -997,7 +1026,8 @@ class AppLocalizationsTr extends AppLocalizations { String get enable_connect => 'Bağlanmayı etkinleştir'; @override - String get enable_connect_description => 'Spotube\'u diğer cihazlardan kontrol edin'; + String get enable_connect_description => + 'Spotube\'u diğer cihazlardan kontrol edin'; @override String get devices => 'Cihazlar'; @@ -1076,7 +1106,8 @@ class AppLocalizationsTr extends AppLocalizations { String get choose_the_device => 'Cihazı seçin:'; @override - String get multiple_device_connected => 'Birden fazla cihaz bağlı.\nBu işlemi gerçekleştirmek istediğiniz cihazı seçin'; + String get multiple_device_connected => + 'Birden fazla cihaz bağlı.\nBu işlemi gerçekleştirmek istediğiniz cihazı seçin'; @override String get nothing_found => 'Hiçbir şey bulunamadı'; @@ -1149,7 +1180,8 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Spotify\'ın akış başına ödeme miktarına\n\$0.003 ile \$0.005 arasında hesaplanmıştır. Bu, kullanıcıya\nSpotify\'da şarkılarını dinlerse sanatçılara ne kadar ödeme\nyapmış olabileceğini göstermek için hipotetik bir hesaplamadır.'; + String get streaming_fees_hypothetical => + '*Spotify\'ın akış başına ödeme miktarına\n\$0.003 ile \$0.005 arasında hesaplanmıştır. Bu, kullanıcıya\nSpotify\'da şarkılarını dinlerse sanatçılara ne kadar ödeme\nyapmış olabileceğini göstermek için hipotetik bir hesaplamadır.'; @override String get minutes_listened => 'Dinlenilen Dakikalar'; @@ -1171,7 +1203,8 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1253,8 @@ class AppLocalizationsTr extends AppLocalizations { String get webview_not_found => 'Webview bulunamadı'; @override - String get webview_not_found_description => 'Cihazınızda herhangi bir Webview çalışma zamanı yüklü değil.\nEğer kuruluysa, ortam YOLUNDA olduğundan emin olun\n\nKurulumdan sonra uygulamayı yeniden başlatın'; + String get webview_not_found_description => + 'Cihazınızda herhangi bir Webview çalışma zamanı yüklü değil.\nEğer kuruluysa, ortam YOLUNDA olduğundan emin olun\n\nKurulumdan sonra uygulamayı yeniden başlatın'; @override String get unsupported_platform => 'Desteklenmeyen platform'; @@ -1241,7 +1275,8 @@ class AppLocalizationsTr extends AppLocalizations { String get clear_cache => 'Önbelleği temizle'; @override - String get clear_cache_confirmation => 'Önbelleği temizlemek istiyor musunuz?'; + String get clear_cache_confirmation => + 'Önbelleği temizlemek istiyor musunuz?'; @override String get export_cache_files => 'Önbelleğe Alınmış Dosyaları Dışa Aktar'; @@ -1252,7 +1287,8 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get export_cache_confirmation => 'Bu dosyaları dışa aktarmak istiyor musunuz'; + String get export_cache_confirmation => + 'Bu dosyaları dışa aktarmak istiyor musunuz'; @override String exported_n_out_of_m_files(Object files, Object filesExported) { @@ -1281,19 +1317,22 @@ class AppLocalizationsTr extends AppLocalizations { String get view_all => 'Tümünü Gör'; @override - String get no_tracks_added_yet => 'Henüz hiçbir şarkı eklemediniz gibi görünüyor'; + String get no_tracks_added_yet => + 'Henüz hiçbir şarkı eklemediniz gibi görünüyor'; @override String get no_tracks => 'Burada hiç şarkı yok gibi görünüyor'; @override - String get no_tracks_listened_yet => 'Henüz hiçbir şey dinlemediniz gibi görünüyor'; + String get no_tracks_listened_yet => + 'Henüz hiçbir şey dinlemediniz gibi görünüyor'; @override String get not_following_artists => 'Hiçbir sanatçıyı takip etmiyorsunuz'; @override - String get no_favorite_albums_yet => 'Henüz favorilerinize herhangi bir albüm eklemediniz gibi görünüyor'; + String get no_favorite_albums_yet => + 'Henüz favorilerinize herhangi bir albüm eklemediniz gibi görünüyor'; @override String get no_logs_found => 'Log bulunamadı'; @@ -1317,7 +1356,8 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'macOS/Linux/Unix benzeri işletim sistemlerinde, .zshrc/.bashrc/.bash_profile gibi dosyalarda yol ayarlamak işe yaramaz.\nYolunuzu kabuk yapılandırma dosyasına ayarlamanız gerekir'; + String get youtube_engine_unix_issue_message => + 'macOS/Linux/Unix benzeri işletim sistemlerinde, .zshrc/.bashrc/.bash_profile gibi dosyalarda yol ayarlamak işe yaramaz.\nYolunuzu kabuk yapılandırma dosyasına ayarlamanız gerekir'; @override String get download => 'İndir'; @@ -1335,7 +1375,8 @@ class AppLocalizationsTr extends AppLocalizations { String get edit_port => 'Portu düzenle'; @override - String get port_helper_msg => 'Varsayılan -1\'dir, bu da rastgele bir sayıyı gösterir. Bir güvenlik duvarınız varsa, bunu ayarlamanız önerilir.'; + String get port_helper_msg => + 'Varsayılan -1\'dir, bu da rastgele bir sayıyı gösterir. Bir güvenlik duvarınız varsa, bunu ayarlamanız önerilir.'; @override String connect_request(Object client) { @@ -1343,5 +1384,6 @@ class AppLocalizationsTr extends AppLocalizations { } @override - String get connection_request_denied => 'Bağlantı reddedildi. Kullanıcı erişimi reddetti.'; + String get connection_request_denied => + 'Bağlantı reddedildi. Kullanıcı erişimi reddetti.'; } diff --git a/lib/l10n/generated/app_localizations_uk.dart b/lib/l10n/generated/app_localizations_uk.dart index 308e80f9..51ab48d5 100644 --- a/lib/l10n/generated/app_localizations_uk.dart +++ b/lib/l10n/generated/app_localizations_uk.dart @@ -309,13 +309,15 @@ class AppLocalizationsUk extends AppLocalizations { String get mini_player => 'Міні-плеєр'; @override - String get slide_to_seek => 'Проведіть пальцем, щоб перемотати вперед або назад'; + String get slide_to_seek => + 'Проведіть пальцем, щоб перемотати вперед або назад'; @override String get shuffle_playlist => 'Випадковий порядок відтворення плейлиста'; @override - String get unshuffle_playlist => 'Відключити випадковий порядок відтворення плейлиста'; + String get unshuffle_playlist => + 'Відключити випадковий порядок відтворення плейлиста'; @override String get previous_track => 'Попередній трек'; @@ -356,7 +358,8 @@ class AppLocalizationsUk extends AppLocalizations { String get clear_all => 'Очистити все'; @override - String get show_hide_ui_on_hover => 'Показувати/приховувати інтерфейс при наведенні курсору'; + String get show_hide_ui_on_hover => + 'Показувати/приховувати інтерфейс при наведенні курсору'; @override String get always_on_top => 'Завжди зверху'; @@ -407,7 +410,8 @@ class AppLocalizationsUk extends AppLocalizations { String get layout_mode => 'Режим макета'; @override - String get override_layout_settings => 'Перезаписати налаштування адаптивного режиму макета'; + String get override_layout_settings => + 'Перезаписати налаштування адаптивного режиму макета'; @override String get adaptive => 'Адаптивний'; @@ -437,7 +441,8 @@ class AppLocalizationsUk extends AppLocalizations { String get sync_album_color => 'Синхронізувати колір альбому'; @override - String get sync_album_color_description => 'Використовує домінуючий колір обкладинки альбому як колір акценту'; + String get sync_album_color_description => + 'Використовує домінуючий колір обкладинки альбому як колір акценту'; @override String get playback => 'Відтворення'; @@ -455,7 +460,8 @@ class AppLocalizationsUk extends AppLocalizations { String get pre_download_play => 'Попереднє завантаження та відтворення'; @override - String get pre_download_play_description => 'Замість потокового відтворення аудіо завантажте байти та відтворіть їх (рекомендовано для користувачів з високою пропускною здатністю)'; + String get pre_download_play_description => + 'Замість потокового відтворення аудіо завантажте байти та відтворіть їх (рекомендовано для користувачів з високою пропускною здатністю)'; @override String get skip_non_music => 'Пропустити не музичні сегменти'; @@ -464,7 +470,8 @@ class AppLocalizationsUk extends AppLocalizations { String get blacklist_description => 'Треки та виконавці в чорному списку'; @override - String get wait_for_download_to_finish => 'Зачекайте, поки завершиться поточна загрузка'; + String get wait_for_download_to_finish => + 'Зачекайте, поки завершиться поточна загрузка'; @override String get desktop => 'Робочий стіл'; @@ -500,7 +507,8 @@ class AppLocalizationsUk extends AppLocalizations { String get please_sponsor => 'Будь ласка, станьте спонсором/зробіть пожертву'; @override - String get spotube_description => 'Spotube, легкий, кросплатформовий, безкоштовний клієнт Spotify'; + String get spotube_description => + 'Spotube, легкий, кросплатформовий, безкоштовний клієнт Spotify'; @override String get version => 'Версія'; @@ -532,7 +540,8 @@ class AppLocalizationsUk extends AppLocalizations { String get license => 'Ліцензія'; @override - String get credentials_will_not_be_shared_disclaimer => 'Не хвилюйтеся, жодні ваші облікові дані не будуть зібрані або передані кому-небудь'; + String get credentials_will_not_be_shared_disclaimer => + 'Не хвилюйтеся, жодні ваші облікові дані не будуть зібрані або передані кому-небудь'; @override String get know_how_to_login => 'Не знаєте, як це зробити?'; @@ -576,19 +585,23 @@ class AppLocalizationsUk extends AppLocalizations { String get piped_instance => 'Примірник сервера Piped'; @override - String get piped_description => 'Примірник сервера Piped, який використовуватиметься для зіставлення треків'; + String get piped_description => + 'Примірник сервера Piped, який використовуватиметься для зіставлення треків'; @override - String get piped_warning => 'Деякі з них можуть працювати неправильно. Тому використовуйте на свій страх і ризик'; + String get piped_warning => + 'Деякі з них можуть працювати неправильно. Тому використовуйте на свій страх і ризик'; @override String get invidious_instance => 'Екземпляр сервера Invidious'; @override - String get invidious_description => 'Екземпляр сервера Invidious для зіставлення треків'; + String get invidious_description => + 'Екземпляр сервера Invidious для зіставлення треків'; @override - String get invidious_warning => 'Деякі можуть працювати не дуже добре. Використовуйте на власний ризик'; + String get invidious_warning => + 'Деякі можуть працювати не дуже добре. Використовуйте на власний ризик'; @override String get generate => 'Генерувати'; @@ -602,7 +615,8 @@ class AppLocalizationsUk extends AppLocalizations { String get replace_downloaded_tracks => 'Замінити всі завантажені треки'; @override - String get skip_download_tracks => 'Пропустити завантаження всіх завантажених треків'; + String get skip_download_tracks => + 'Пропустити завантаження всіх завантажених треків'; @override String get do_you_want_to_replace => 'Ви хочете замінити існуючий трек?'; @@ -703,7 +717,8 @@ class AppLocalizationsUk extends AppLocalizations { String get are_you_sure => 'Ви впевнені?'; @override - String get generating_playlist => 'Створення вашого персонального плейлиста...'; + String get generating_playlist => + 'Створення вашого персонального плейлиста...'; @override String selected_count_tracks(Object count) { @@ -711,22 +726,27 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get download_warning => 'Якщо ви завантажуєте всі треки масово, ви явно піратствуєте і завдаєте шкоди музичному творчому співтовариству. Сподіваюся, ви усвідомлюєте це. Завжди намагайтеся поважати і підтримувати важку працю артиста'; + String get download_warning => + 'Якщо ви завантажуєте всі треки масово, ви явно піратствуєте і завдаєте шкоди музичному творчому співтовариству. Сподіваюся, ви усвідомлюєте це. Завжди намагайтеся поважати і підтримувати важку працю артиста'; @override - String get download_ip_ban_warning => 'До речі, ваш IP може бути заблокований на YouTube через надмірну кількість запитів на завантаження, ніж зазвичай. Блокування IP-адреси означає, що ви не зможете користуватися YouTube (навіть якщо ви увійшли в систему) протягом щонайменше 2-3 місяців з цього пристрою. І Spotube не несе жодної відповідальності, якщо це станеться'; + String get download_ip_ban_warning => + 'До речі, ваш IP може бути заблокований на YouTube через надмірну кількість запитів на завантаження, ніж зазвичай. Блокування IP-адреси означає, що ви не зможете користуватися YouTube (навіть якщо ви увійшли в систему) протягом щонайменше 2-3 місяців з цього пристрою. І Spotube не несе жодної відповідальності, якщо це станеться'; @override - String get by_clicking_accept_terms => 'Натискаючи \'прийняти\', ви погоджуєтеся з наступними умовами:'; + String get by_clicking_accept_terms => + 'Натискаючи \'прийняти\', ви погоджуєтеся з наступними умовами:'; @override String get download_agreement_1 => 'Я знаю, що краду музику. Я поганий.'; @override - String get download_agreement_2 => 'Я підтримаю автора, де тільки зможу, і роблю це лише тому, що не маю грошей, щоб купити його роботи.'; + String get download_agreement_2 => + 'Я підтримаю автора, де тільки зможу, і роблю це лише тому, що не маю грошей, щоб купити його роботи.'; @override - String get download_agreement_3 => 'Я повністю усвідомлюю, що мій IP може бути заблокований на YouTube, і я не покладаю на Spotube або його власників/контрибуторів відповідальність за будь-які нещасні випадки, спричинені моїми діями.'; + String get download_agreement_3 => + 'Я повністю усвідомлюю, що мій IP може бути заблокований на YouTube, і я не покладаю на Spotube або його власників/контрибуторів відповідальність за будь-які нещасні випадки, спричинені моїми діями.'; @override String get decline => 'Відхилити'; @@ -807,7 +827,8 @@ class AppLocalizationsUk extends AppLocalizations { String get failed_to_encrypt => 'Не вдалося зашифрувати'; @override - String get encryption_failed_warning => 'Spotube використовує шифрування для безпечного зберігання ваших даних. Але не вдалося цього зробити. Тому він перейде до небезпечного зберігання\nЯкщо ви використовуєте Linux, переконайтеся, що у вас встановлено будь-який секретний сервіс (gnome-keyring, kde-wallet, keepassxc тощо)'; + String get encryption_failed_warning => + 'Spotube використовує шифрування для безпечного зберігання ваших даних. Але не вдалося цього зробити. Тому він перейде до небезпечного зберігання\nЯкщо ви використовуєте Linux, переконайтеся, що у вас встановлено будь-який секретний сервіс (gnome-keyring, kde-wallet, keepassxc тощо)'; @override String get querying_info => 'Запит інформації...'; @@ -905,7 +926,8 @@ class AppLocalizationsUk extends AppLocalizations { String get friends => 'Друзі'; @override - String get no_lyrics_available => 'Вибачте, не вдалося знайти текст для цього треку'; + String get no_lyrics_available => + 'Вибачте, не вдалося знайти текст для цього треку'; @override String get start_a_radio => 'Запустити радіо'; @@ -914,7 +936,8 @@ class AppLocalizationsUk extends AppLocalizations { String get how_to_start_radio => 'Як ви хочете запустити радіо?'; @override - String get replace_queue_question => 'Ви хочете замінити поточну чергу чи додати до неї?'; + String get replace_queue_question => + 'Ви хочете замінити поточну чергу чи додати до неї?'; @override String get endless_playback => 'Безкінечне відтворення'; @@ -923,7 +946,8 @@ class AppLocalizationsUk extends AppLocalizations { String get delete_playlist => 'Видалити плейлист'; @override - String get delete_playlist_confirmation => 'Ви впевнені, що хочете видалити цей плейлист?'; + String get delete_playlist_confirmation => + 'Ви впевнені, що хочете видалити цей плейлист?'; @override String get local_tracks => 'Місцеві треки'; @@ -947,16 +971,20 @@ class AppLocalizationsUk extends AppLocalizations { String get get_started => 'Давайте почнемо'; @override - String get youtube_source_description => 'Рекомендовано та працює краще за все.'; + String get youtube_source_description => + 'Рекомендовано та працює краще за все.'; @override - String get piped_source_description => 'Чи почуваєте себе вільно? Те саме, що і на YouTube, але набагато безкоштовно.'; + String get piped_source_description => + 'Чи почуваєте себе вільно? Те саме, що і на YouTube, але набагато безкоштовно.'; @override - String get jiosaavn_source_description => 'Найкраще для регіону Південної Азії.'; + String get jiosaavn_source_description => + 'Найкраще для регіону Південної Азії.'; @override - String get invidious_source_description => 'Подібний до Piped, але з вищою доступністю.'; + String get invidious_source_description => + 'Подібний до Piped, але з вищою доступністю.'; @override String highest_quality(Object quality) { @@ -967,13 +995,15 @@ class AppLocalizationsUk extends AppLocalizations { String get select_audio_source => 'Виберіть джерело аудіо'; @override - String get endless_playback_description => 'Автоматично додавати нові пісні\nв кінець черги'; + String get endless_playback_description => + 'Автоматично додавати нові пісні\nв кінець черги'; @override String get choose_your_region => 'Виберіть ваш регіон'; @override - String get choose_your_region_description => 'Це допоможе Spotube показати вам правильний контент\nдля вашого місцезнаходження.'; + String get choose_your_region_description => + 'Це допоможе Spotube показати вам правильний контент\nдля вашого місцезнаходження.'; @override String get choose_your_language => 'Виберіть свою мову'; @@ -982,7 +1012,8 @@ class AppLocalizationsUk extends AppLocalizations { String get help_project_grow => 'Допоможіть цьому проекту рости'; @override - String get help_project_grow_description => 'Spotube - це проект з відкритим кодом. Ви можете допомогти цьому проекту зростати, вносячи свій внесок у проект, повідомляючи про помилки або пропонуючи нові функції.'; + String get help_project_grow_description => + 'Spotube - це проект з відкритим кодом. Ви можете допомогти цьому проекту зростати, вносячи свій внесок у проект, повідомляючи про помилки або пропонуючи нові функції.'; @override String get contribute_on_github => 'Долучайтесь на GitHub'; @@ -1076,7 +1107,8 @@ class AppLocalizationsUk extends AppLocalizations { String get choose_the_device => 'Виберіть пристрій:'; @override - String get multiple_device_connected => 'Підключено кілька пристроїв.\nВиберіть пристрій, на якому ви хочете виконати цю дію'; + String get multiple_device_connected => + 'Підключено кілька пристроїв.\nВиберіть пристрій, на якому ви хочете виконати цю дію'; @override String get nothing_found => 'Нічого не знайдено'; @@ -1149,7 +1181,8 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Розраховано на основі виплат Spotify за стримінг\nвід \$0.003 до \$0.005. Це гіпотетичний\nрозрахунок, щоб дати уявлення користувачу про те, скільки б він\nзаплатив артистам, якби слухав їхні пісні на Spotify.'; + String get streaming_fees_hypothetical => + '*Розраховано на основі виплат Spotify за стримінг\nвід \$0.003 до \$0.005. Це гіпотетичний\nрозрахунок, щоб дати уявлення користувачу про те, скільки б він\nзаплатив артистам, якби слухав їхні пісні на Spotify.'; @override String get minutes_listened => 'Хвилини прослуховування'; @@ -1171,7 +1204,8 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1254,8 @@ class AppLocalizationsUk extends AppLocalizations { String get webview_not_found => 'Webview не знайдено'; @override - String get webview_not_found_description => 'На вашому пристрої не встановлено виконуване середовище Webview.\nЯкщо воно встановлено, переконайтеся, що воно знаходиться в environment PATH\n\nПісля встановлення перезапустіть програму'; + String get webview_not_found_description => + 'На вашому пристрої не встановлено виконуване середовище Webview.\nЯкщо воно встановлено, переконайтеся, що воно знаходиться в environment PATH\n\nПісля встановлення перезапустіть програму'; @override String get unsupported_platform => 'Непідтримувана платформа'; @@ -1293,7 +1328,8 @@ class AppLocalizationsUk extends AppLocalizations { String get not_following_artists => 'Ви не підписані на жодного артиста'; @override - String get no_favorite_albums_yet => 'Здається, ви ще не додали жодного альбому в улюблені'; + String get no_favorite_albums_yet => + 'Здається, ви ще не додали жодного альбому в улюблені'; @override String get no_logs_found => 'Жодних журналів не знайдено'; @@ -1317,7 +1353,8 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'У macOS/Linux/Unix-подібних ОС, встановлення шляху в .zshrc/.bashrc/.bash_profile тощо не працює.\nВам потрібно налаштувати шлях у файлі конфігурації оболонки'; + String get youtube_engine_unix_issue_message => + 'У macOS/Linux/Unix-подібних ОС, встановлення шляху в .zshrc/.bashrc/.bash_profile тощо не працює.\nВам потрібно налаштувати шлях у файлі конфігурації оболонки'; @override String get download => 'Завантажити'; @@ -1335,7 +1372,8 @@ class AppLocalizationsUk extends AppLocalizations { String get edit_port => 'Редагувати порт'; @override - String get port_helper_msg => 'За замовчуванням -1, що означає випадкове число. Якщо у вас налаштований брандмауер, рекомендується це налаштувати.'; + String get port_helper_msg => + 'За замовчуванням -1, що означає випадкове число. Якщо у вас налаштований брандмауер, рекомендується це налаштувати.'; @override String connect_request(Object client) { @@ -1343,5 +1381,6 @@ class AppLocalizationsUk extends AppLocalizations { } @override - String get connection_request_denied => 'Підключення відхилено. Користувач відмовив у доступі.'; + String get connection_request_denied => + 'Підключення відхилено. Користувач відмовив у доступі.'; } diff --git a/lib/l10n/generated/app_localizations_vi.dart b/lib/l10n/generated/app_localizations_vi.dart index 5e842438..c7f339bc 100644 --- a/lib/l10n/generated/app_localizations_vi.dart +++ b/lib/l10n/generated/app_localizations_vi.dart @@ -356,7 +356,8 @@ class AppLocalizationsVi extends AppLocalizations { String get clear_all => 'Xóa tất cả'; @override - String get show_hide_ui_on_hover => 'Hiển thị/Ẩn giao diện người dùng khi di chuột qua'; + String get show_hide_ui_on_hover => + 'Hiển thị/Ẩn giao diện người dùng khi di chuột qua'; @override String get always_on_top => 'Luôn ở trên cùng'; @@ -437,7 +438,8 @@ class AppLocalizationsVi extends AppLocalizations { String get sync_album_color => 'Đồng bộ màu album'; @override - String get sync_album_color_description => 'Sử dụng màu chủ đạo của hình ảnh album làm màu nhấn'; + String get sync_album_color_description => + 'Sử dụng màu chủ đạo của hình ảnh album làm màu nhấn'; @override String get playback => 'Phát'; @@ -455,7 +457,8 @@ class AppLocalizationsVi extends AppLocalizations { String get pre_download_play => 'Tải xuống và phát'; @override - String get pre_download_play_description => 'Thay vì stream âm thanh, tải xuống trước và phát (Khuyến nghị cho người dùng có băng thông cao)'; + String get pre_download_play_description => + 'Thay vì stream âm thanh, tải xuống trước và phát (Khuyến nghị cho người dùng có băng thông cao)'; @override String get skip_non_music => 'Bỏ qua các đoạn không phải nhạc (SponsorBlock)'; @@ -464,7 +467,8 @@ class AppLocalizationsVi extends AppLocalizations { String get blacklist_description => 'Các bài hát và nghệ sĩ trong blacklist'; @override - String get wait_for_download_to_finish => 'Vui lòng đợi quá trình tải xuống hiện tại hoàn thành'; + String get wait_for_download_to_finish => + 'Vui lòng đợi quá trình tải xuống hiện tại hoàn thành'; @override String get desktop => 'Máy tính'; @@ -500,7 +504,8 @@ class AppLocalizationsVi extends AppLocalizations { String get please_sponsor => 'Vui lòng tài trợ/ủng hộ'; @override - String get spotube_description => 'Spotube, một ứng dụng Spotify nhẹ, đa nền tảng và miễn phí'; + String get spotube_description => + 'Spotube, một ứng dụng Spotify nhẹ, đa nền tảng và miễn phí'; @override String get version => 'Phiên bản'; @@ -532,7 +537,8 @@ class AppLocalizationsVi extends AppLocalizations { String get license => 'Giấy phép'; @override - String get credentials_will_not_be_shared_disclaimer => 'Đừng lo, thông tin đăng nhập của bạn sẽ không được thu thập hoặc chia sẻ với bất kỳ ai'; + String get credentials_will_not_be_shared_disclaimer => + 'Đừng lo, thông tin đăng nhập của bạn sẽ không được thu thập hoặc chia sẻ với bất kỳ ai'; @override String get know_how_to_login => 'Không biết cách lấy thông tin đăng nhập?'; @@ -576,19 +582,23 @@ class AppLocalizationsVi extends AppLocalizations { String get piped_instance => 'Phiên bản Server Piped'; @override - String get piped_description => 'Phiên bản Piped để sử dụng cho Track matching'; + String get piped_description => + 'Phiên bản Piped để sử dụng cho Track matching'; @override - String get piped_warning => 'Một số phiên bản Piped có thể không hoạt động tốt'; + String get piped_warning => + 'Một số phiên bản Piped có thể không hoạt động tốt'; @override String get invidious_instance => 'Phiên bản máy chủ Invidious'; @override - String get invidious_description => 'Phiên bản máy chủ Invidious để sử dụng để so khớp bản nhạc'; + String get invidious_description => + 'Phiên bản máy chủ Invidious để sử dụng để so khớp bản nhạc'; @override - String get invidious_warning => 'Một số có thể sẽ không hoạt động tốt. Vì vậy hãy sử dụng với rủi ro của riêng bạn'; + String get invidious_warning => + 'Một số có thể sẽ không hoạt động tốt. Vì vậy hãy sử dụng với rủi ro của riêng bạn'; @override String get generate => 'Tạo'; @@ -602,10 +612,12 @@ class AppLocalizationsVi extends AppLocalizations { String get replace_downloaded_tracks => 'Thay thế tất cả các bài hát đã tải'; @override - String get skip_download_tracks => 'Bỏ qua tải xuống tất cả các bài hát đã tải'; + String get skip_download_tracks => + 'Bỏ qua tải xuống tất cả các bài hát đã tải'; @override - String get do_you_want_to_replace => 'Bạn có muốn thay thế bài hát hiện có không?'; + String get do_you_want_to_replace => + 'Bạn có muốn thay thế bài hát hiện có không?'; @override String get replace => 'Thay thế'; @@ -703,7 +715,8 @@ class AppLocalizationsVi extends AppLocalizations { String get are_you_sure => 'Bạn có chắc chắn?'; @override - String get generating_playlist => 'Đang tạo danh sách phát tùy chỉnh của bạn...'; + String get generating_playlist => + 'Đang tạo danh sách phát tùy chỉnh của bạn...'; @override String selected_count_tracks(Object count) { @@ -711,22 +724,28 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get download_warning => 'Tải xuống tất cả các bài hát một lần, sẽ vi phạm bản quyền âm nhạc và gây thiệt hại cho xã hội sáng tạo âm nhạc. Hy vọng bạn nhận thức được điều này. Hãy luôn tôn trọng và ủng hộ công sức của nghệ sĩ'; + String get download_warning => + 'Tải xuống tất cả các bài hát một lần, sẽ vi phạm bản quyền âm nhạc và gây thiệt hại cho xã hội sáng tạo âm nhạc. Hy vọng bạn nhận thức được điều này. Hãy luôn tôn trọng và ủng hộ công sức của nghệ sĩ'; @override - String get download_ip_ban_warning => 'Địa chỉ IP của bạn có thể bị chặn trên YouTube do yêu cầu tải xuống quá mức so với bình thường. Chặn IP có nghĩa là bạn không thể sử dụng YouTube (ngay cả khi bạn đã đăng nhập) ít nhất 2-3 tháng từ thiết bị IP đó. Và Spotube không chịu trách nhiệm nếu điều này xảy ra'; + String get download_ip_ban_warning => + 'Địa chỉ IP của bạn có thể bị chặn trên YouTube do yêu cầu tải xuống quá mức so với bình thường. Chặn IP có nghĩa là bạn không thể sử dụng YouTube (ngay cả khi bạn đã đăng nhập) ít nhất 2-3 tháng từ thiết bị IP đó. Và Spotube không chịu trách nhiệm nếu điều này xảy ra'; @override - String get by_clicking_accept_terms => 'Bằng cách nhấp vào \'Chấp nhận\', bạn đồng ý với các điều khoản sau:'; + String get by_clicking_accept_terms => + 'Bằng cách nhấp vào \'Chấp nhận\', bạn đồng ý với các điều khoản sau:'; @override - String get download_agreement_1 => 'Tôi biết mình đang vi phạm bản quyền âm nhạc. Đó là không tốt.'; + String get download_agreement_1 => + 'Tôi biết mình đang vi phạm bản quyền âm nhạc. Đó là không tốt.'; @override - String get download_agreement_2 => 'Tôi sẽ ủng hộ nghệ sĩ bất cứ nơi nào tôi có thể và tôi chỉ làm điều này vì tôi không có tiền để mua tác phẩm của họ'; + String get download_agreement_2 => + 'Tôi sẽ ủng hộ nghệ sĩ bất cứ nơi nào tôi có thể và tôi chỉ làm điều này vì tôi không có tiền để mua tác phẩm của họ'; @override - String get download_agreement_3 => 'Tôi hoàn toàn nhận thức được rằng địa chỉ IP của tôi có thể bị chặn trên YouTube và tôi không đổ lỗi cho Spotube hoặc chủ sở hữu/người đóng góp của nó về bất kỳ tai nạn nào do hành động này của tôi'; + String get download_agreement_3 => + 'Tôi hoàn toàn nhận thức được rằng địa chỉ IP của tôi có thể bị chặn trên YouTube và tôi không đổ lỗi cho Spotube hoặc chủ sở hữu/người đóng góp của nó về bất kỳ tai nạn nào do hành động này của tôi'; @override String get decline => 'Từ chối'; @@ -807,7 +826,8 @@ class AppLocalizationsVi extends AppLocalizations { String get failed_to_encrypt => 'Mã hóa không thành công'; @override - String get encryption_failed_warning => 'Spotube không thành công trong việc mã hóa nhằm lưu trữ dữ liêu an toàn. vậy nên sẽ chuyển về lưu trữ không an toàn\nNếu bạn đang sử dụng Linux, đảm bảo rằng bạn có sử dụng dịch vụ bảo mật (gnome-keyring, kde-wallet, keepassxc, v.v.)'; + String get encryption_failed_warning => + 'Spotube không thành công trong việc mã hóa nhằm lưu trữ dữ liêu an toàn. vậy nên sẽ chuyển về lưu trữ không an toàn\nNếu bạn đang sử dụng Linux, đảm bảo rằng bạn có sử dụng dịch vụ bảo mật (gnome-keyring, kde-wallet, keepassxc, v.v.)'; @override String get querying_info => 'Đang truy vấn thông tin...'; @@ -824,7 +844,8 @@ class AppLocalizationsVi extends AppLocalizations { String get you_are_offline => 'Bạn đang ngoại tuyến'; @override - String get connection_restored => 'Kết nối internet của bạn đã được khôi phục'; + String get connection_restored => + 'Kết nối internet của bạn đã được khôi phục'; @override String get use_system_title_bar => 'Sử dụng thanh tiêu đề hệ thống'; @@ -881,7 +902,8 @@ class AppLocalizationsVi extends AppLocalizations { String get login => 'Đăng nhập'; @override - String get login_with_your_lastfm => 'Đăng nhập bằng tài khoản Last.fm của bạn'; + String get login_with_your_lastfm => + 'Đăng nhập bằng tài khoản Last.fm của bạn'; @override String get scrobble_to_lastfm => 'Scrobble đến Last.fm'; @@ -905,16 +927,19 @@ class AppLocalizationsVi extends AppLocalizations { String get friends => 'Bạn bè'; @override - String get no_lyrics_available => 'Xin lỗi, không tìm thấy lời cho bài hát này'; + String get no_lyrics_available => + 'Xin lỗi, không tìm thấy lời cho bài hát này'; @override String get start_a_radio => 'Bắt đầu Một Đài phát thanh'; @override - String get how_to_start_radio => 'Bạn muốn bắt đầu đài phát thanh như thế nào?'; + String get how_to_start_radio => + 'Bạn muốn bắt đầu đài phát thanh như thế nào?'; @override - String get replace_queue_question => 'Bạn muốn thay thế hàng đợi hiện tại hay thêm vào?'; + String get replace_queue_question => + 'Bạn muốn thay thế hàng đợi hiện tại hay thêm vào?'; @override String get endless_playback => 'Phát không giới hạn'; @@ -923,7 +948,8 @@ class AppLocalizationsVi extends AppLocalizations { String get delete_playlist => 'Xóa Danh sách phát'; @override - String get delete_playlist_confirmation => 'Bạn có chắc chắn muốn xóa danh sách phát này không?'; + String get delete_playlist_confirmation => + 'Bạn có chắc chắn muốn xóa danh sách phát này không?'; @override String get local_tracks => 'Bài hát Địa phương'; @@ -941,22 +967,26 @@ class AppLocalizationsVi extends AppLocalizations { String get freedom_of_music => '“Sự Tự do của Âm nhạc”'; @override - String get freedom_of_music_palm => '“Sự Tự do của Âm nhạc trong lòng bàn tay của bạn”'; + String get freedom_of_music_palm => + '“Sự Tự do của Âm nhạc trong lòng bàn tay của bạn”'; @override String get get_started => 'Bắt đầu thôi'; @override - String get youtube_source_description => 'Được đề xuất và hoạt động tốt nhất.'; + String get youtube_source_description => + 'Được đề xuất và hoạt động tốt nhất.'; @override - String get piped_source_description => 'Cảm thấy tự do? Giống như YouTube nhưng miễn phí hơn rất nhiều.'; + String get piped_source_description => + 'Cảm thấy tự do? Giống như YouTube nhưng miễn phí hơn rất nhiều.'; @override String get jiosaavn_source_description => 'Tốt nhất cho khu vực Nam Á.'; @override - String get invidious_source_description => 'Tương tự như Piped nhưng có tính khả dụng cao hơn.'; + String get invidious_source_description => + 'Tương tự như Piped nhưng có tính khả dụng cao hơn.'; @override String highest_quality(Object quality) { @@ -967,13 +997,15 @@ class AppLocalizationsVi extends AppLocalizations { String get select_audio_source => 'Chọn Nguồn Âm thanh'; @override - String get endless_playback_description => 'Tự động thêm các bài hát mới\nvào cuối hàng đợi'; + String get endless_playback_description => + 'Tự động thêm các bài hát mới\nvào cuối hàng đợi'; @override String get choose_your_region => 'Chọn khu vực của bạn'; @override - String get choose_your_region_description => 'Điều này sẽ giúp Spotube hiển thị nội dung phù hợp cho vị trí của bạn.'; + String get choose_your_region_description => + 'Điều này sẽ giúp Spotube hiển thị nội dung phù hợp cho vị trí của bạn.'; @override String get choose_your_language => 'Chọn ngôn ngữ của bạn'; @@ -982,7 +1014,8 @@ class AppLocalizationsVi extends AppLocalizations { String get help_project_grow => 'Hãy giúp dự án này phát triển'; @override - String get help_project_grow_description => 'Spotube là một dự án mã nguồn mở. Bạn có thể giúp dự án này phát triển bằng cách đóng góp vào dự án, báo cáo lỗi hoặc đề xuất tính năng mới.'; + String get help_project_grow_description => + 'Spotube là một dự án mã nguồn mở. Bạn có thể giúp dự án này phát triển bằng cách đóng góp vào dự án, báo cáo lỗi hoặc đề xuất tính năng mới.'; @override String get contribute_on_github => 'Đóng góp trên GitHub'; @@ -997,7 +1030,8 @@ class AppLocalizationsVi extends AppLocalizations { String get enable_connect => 'Kích hoạt kết nối'; @override - String get enable_connect_description => 'Điều khiển Spotube từ các thiết bị khác'; + String get enable_connect_description => + 'Điều khiển Spotube từ các thiết bị khác'; @override String get devices => 'Thiết bị'; @@ -1076,7 +1110,8 @@ class AppLocalizationsVi extends AppLocalizations { String get choose_the_device => 'Chọn thiết bị:'; @override - String get multiple_device_connected => 'Có nhiều thiết bị kết nối.\nChọn thiết bị mà bạn muốn thực hiện hành động này'; + String get multiple_device_connected => + 'Có nhiều thiết bị kết nối.\nChọn thiết bị mà bạn muốn thực hiện hành động này'; @override String get nothing_found => 'Không tìm thấy gì'; @@ -1149,7 +1184,8 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*Tính toán dựa trên thanh toán của Spotify cho mỗi lần phát\ntừ \$0.003 đến \$0.005. Đây là một tính toán giả định để\ngive người dùng cái nhìn về số tiền họ sẽ chi trả cho các nghệ sĩ nếu họ nghe\nbài hát của họ trên Spotify.'; + String get streaming_fees_hypothetical => + '*Tính toán dựa trên thanh toán của Spotify cho mỗi lần phát\ntừ \$0.003 đến \$0.005. Đây là một tính toán giả định để\ngive người dùng cái nhìn về số tiền họ sẽ chi trả cho các nghệ sĩ nếu họ nghe\nbài hát của họ trên Spotify.'; @override String get minutes_listened => 'Thời gian nghe'; @@ -1171,7 +1207,8 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1257,8 @@ class AppLocalizationsVi extends AppLocalizations { String get webview_not_found => 'Không tìm thấy Webview'; @override - String get webview_not_found_description => 'Không có runtime Webview nào được cài đặt trên thiết bị của bạn.\nNếu đã cài đặt, hãy đảm bảo rằng nó nằm trong environment PATH\n\nSau khi cài đặt, hãy khởi động lại ứng dụng'; + String get webview_not_found_description => + 'Không có runtime Webview nào được cài đặt trên thiết bị của bạn.\nNếu đã cài đặt, hãy đảm bảo rằng nó nằm trong environment PATH\n\nSau khi cài đặt, hãy khởi động lại ứng dụng'; @override String get unsupported_platform => 'Nền tảng không được hỗ trợ'; @@ -1290,10 +1328,12 @@ class AppLocalizationsVi extends AppLocalizations { String get no_tracks_listened_yet => 'Có vẻ bạn chưa nghe gì cả'; @override - String get not_following_artists => 'Bạn không đang theo dõi bất kỳ nghệ sĩ nào'; + String get not_following_artists => + 'Bạn không đang theo dõi bất kỳ nghệ sĩ nào'; @override - String get no_favorite_albums_yet => 'Có vẻ bạn chưa thêm album nào vào danh sách yêu thích'; + String get no_favorite_albums_yet => + 'Có vẻ bạn chưa thêm album nào vào danh sách yêu thích'; @override String get no_logs_found => 'Không tìm thấy nhật ký'; @@ -1317,7 +1357,8 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => 'Trên macOS/Linux/Unix, việc thiết lập đường dẫn trong .zshrc/.bashrc/.bash_profile v.v. sẽ không hoạt động.\nBạn cần thiết lập đường dẫn trong tệp cấu hình shell'; + String get youtube_engine_unix_issue_message => + 'Trên macOS/Linux/Unix, việc thiết lập đường dẫn trong .zshrc/.bashrc/.bash_profile v.v. sẽ không hoạt động.\nBạn cần thiết lập đường dẫn trong tệp cấu hình shell'; @override String get download => 'Tải xuống'; @@ -1335,7 +1376,8 @@ class AppLocalizationsVi extends AppLocalizations { String get edit_port => 'Chỉnh sửa cổng'; @override - String get port_helper_msg => 'Mặc định là -1, có nghĩa là số ngẫu nhiên. Nếu bạn đã cấu hình tường lửa, nên đặt điều này.'; + String get port_helper_msg => + 'Mặc định là -1, có nghĩa là số ngẫu nhiên. Nếu bạn đã cấu hình tường lửa, nên đặt điều này.'; @override String connect_request(Object client) { @@ -1343,5 +1385,6 @@ class AppLocalizationsVi extends AppLocalizations { } @override - String get connection_request_denied => 'Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập.'; + String get connection_request_denied => + 'Kết nối bị từ chối. Người dùng đã từ chối quyền truy cập.'; } diff --git a/lib/l10n/generated/app_localizations_zh.dart b/lib/l10n/generated/app_localizations_zh.dart index d7a6fe84..53a6c3dc 100644 --- a/lib/l10n/generated/app_localizations_zh.dart +++ b/lib/l10n/generated/app_localizations_zh.dart @@ -532,7 +532,8 @@ class AppLocalizationsZh extends AppLocalizations { String get license => '许可证'; @override - String get credentials_will_not_be_shared_disclaimer => '不用担心,软件不会收集或分享任何个人数据给第三方'; + String get credentials_will_not_be_shared_disclaimer => + '不用担心,软件不会收集或分享任何个人数据给第三方'; @override String get know_how_to_login => '不知道该怎么做?'; @@ -711,10 +712,12 @@ class AppLocalizationsZh extends AppLocalizations { } @override - String get download_warning => '如果你大量下载这些歌曲,你显然在侵犯音乐的版权并对音乐创作社区造成了伤害。我希望你能意识到这一点。永远要尊重并支持艺术家们的辛勤工作'; + String get download_warning => + '如果你大量下载这些歌曲,你显然在侵犯音乐的版权并对音乐创作社区造成了伤害。我希望你能意识到这一点。永远要尊重并支持艺术家们的辛勤工作'; @override - String get download_ip_ban_warning => '小心,如果出现超出正常的下载请求那你的 IP 可能会被 YouTube 封禁,这意味着你的设备将在长达 2-3 个月的时间内无法使用该 IP 访问 YouTube(即使你没登录)。Spotube 对此不承担任何责任'; + String get download_ip_ban_warning => + '小心,如果出现超出正常的下载请求那你的 IP 可能会被 YouTube 封禁,这意味着你的设备将在长达 2-3 个月的时间内无法使用该 IP 访问 YouTube(即使你没登录)。Spotube 对此不承担任何责任'; @override String get by_clicking_accept_terms => '点击 \'同意\' 代表着你同意以下的条款'; @@ -726,7 +729,8 @@ class AppLocalizationsZh extends AppLocalizations { String get download_agreement_2 => '我将尽可能支持艺术家的工作。我现在之所以做不到是因为缺乏资金来购买正版'; @override - String get download_agreement_3 => '我完全了解我的 IP 存在被 YouTube的风险。我同意 Spotube 的所有者与贡献者们无须对我目前的行为所导致的任何后果负责'; + String get download_agreement_3 => + '我完全了解我的 IP 存在被 YouTube的风险。我同意 Spotube 的所有者与贡献者们无须对我目前的行为所导致的任何后果负责'; @override String get decline => '拒绝'; @@ -807,7 +811,8 @@ class AppLocalizationsZh extends AppLocalizations { String get failed_to_encrypt => '加密失败'; @override - String get encryption_failed_warning => 'Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务'; + String get encryption_failed_warning => + 'Spotube使用加密来安全地存储您的数据。但是失败了。因此,它将回退到不安全的存储\n如果您使用Linux,请确保已安装gnome-keyring、kde-wallet和keepassxc等秘密服务'; @override String get querying_info => '正在查询信息...'; @@ -982,7 +987,8 @@ class AppLocalizationsZh extends AppLocalizations { String get help_project_grow => '帮助这个项目成长'; @override - String get help_project_grow_description => 'Spotube是一个开源项目。您可以通过为项目做出贡献、报告错误或建议新功能来帮助该项目成长。'; + String get help_project_grow_description => + 'Spotube是一个开源项目。您可以通过为项目做出贡献、报告错误或建议新功能来帮助该项目成长。'; @override String get contribute_on_github => '在GitHub上做出贡献'; @@ -1149,7 +1155,8 @@ class AppLocalizationsZh extends AppLocalizations { } @override - String get streaming_fees_hypothetical => '*基于 Spotify 每次播放的支付金额\n从 \$0.003 到 \$0.005 计算。这是一个假设性的\n计算,旨在让用户了解如果他们在 Spotify 上收听\n这些歌曲,可能会付给艺术家的金额。'; + String get streaming_fees_hypothetical => + '*基于 Spotify 每次播放的支付金额\n从 \$0.003 到 \$0.005 计算。这是一个假设性的\n计算,旨在让用户了解如果他们在 Spotify 上收听\n这些歌曲,可能会付给艺术家的金额。'; @override String get minutes_listened => '听的分钟数'; @@ -1171,7 +1178,8 @@ class AppLocalizationsZh extends AppLocalizations { } @override - String get hipotetical_calculation => '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; + String get hipotetical_calculation => + '*This is calculated based on average online music streaming platform\'s per stream\npayout of \$0.003 to \$0.005. This is a hypothetical\ncalculation to give user insight about how much they\nwould have paid to the artists if they were to listen\ntheir song in different music streaming platform.'; @override String count_mins(Object minutes) { @@ -1220,7 +1228,8 @@ class AppLocalizationsZh extends AppLocalizations { String get webview_not_found => '未找到 Webview'; @override - String get webview_not_found_description => '您的设备中未安装 Webview 运行时。\n如果已安装,请确保它在 environment PATH 中\n\n安装后,重新启动应用程序'; + String get webview_not_found_description => + '您的设备中未安装 Webview 运行时。\n如果已安装,请确保它在 environment PATH 中\n\n安装后,重新启动应用程序'; @override String get unsupported_platform => '不支持的平台'; @@ -1317,7 +1326,8 @@ class AppLocalizationsZh extends AppLocalizations { } @override - String get youtube_engine_unix_issue_message => '在 macOS/Linux/Unix 类操作系统中,在 .zshrc/.bashrc/.bash_profile 等文件中设置路径无效。\n您需要在 shell 配置文件中设置路径'; + String get youtube_engine_unix_issue_message => + '在 macOS/Linux/Unix 类操作系统中,在 .zshrc/.bashrc/.bash_profile 等文件中设置路径无效。\n您需要在 shell 配置文件中设置路径'; @override String get download => '下载'; diff --git a/lib/pages/settings/metadata/metadata_form.dart b/lib/pages/settings/metadata/metadata_form.dart index 3cd29937..7ddddfb9 100644 --- a/lib/pages/settings/metadata/metadata_form.dart +++ b/lib/pages/settings/metadata/metadata_form.dart @@ -9,6 +9,7 @@ import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; +import 'package:url_launcher/url_launcher_string.dart'; @RoutePage() class SettingsMetadataProviderFormPage extends HookConsumerWidget { @@ -54,7 +55,15 @@ class SettingsMetadataProviderFormPage extends HookConsumerWidget { if (fields[index] is MetadataFormFieldTextObject) { final field = fields[index] as MetadataFormFieldTextObject; - return MarkdownBody(data: field.text); + return MarkdownBody( + data: field.text, + onTapLink: (text, href, title) { + // TODO: Confirm link opening behavior + if (href != null) { + launchUrlString(href); + } + }, + ); } final field = fields[index] as MetadataFormFieldInputObject; diff --git a/lib/services/audio_services/audio_services.dart b/lib/services/audio_services/audio_services.dart index b3653555..cb27a346 100644 --- a/lib/services/audio_services/audio_services.dart +++ b/lib/services/audio_services/audio_services.dart @@ -7,7 +7,6 @@ import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; import 'package:spotube/services/audio_services/mobile_audio_service.dart'; import 'package:spotube/services/audio_services/windows_audio_service.dart'; -import 'package:spotube/services/sourced_track/sourced_track.dart'; import 'package:spotube/utils/platform.dart'; class AudioServices with WidgetsBindingObserver { @@ -31,13 +30,12 @@ class AudioServices with WidgetsBindingObserver { Env.releaseChannel )) { (true, _) => "spotube", - (_, ReleaseChannel.stable) => "com.krtirtho.Spotube", - (_, ReleaseChannel.nightly) => "com.krtirtho.Spotube.nightly", + (_, ReleaseChannel.stable) => "oss.krtirtho.spotube", + (_, ReleaseChannel.nightly) => "oss.krtirtho.spotube.nightly", }, androidNotificationChannelName: 'Spotube', androidNotificationOngoing: false, androidStopForegroundOnPause: false, - androidNotificationIcon: "drawable/ic_launcher_monochrome", androidNotificationChannelDescription: "Spotube Media Controls", ), ) @@ -51,12 +49,12 @@ class AudioServices with WidgetsBindingObserver { await smtc?.addTrack(track); mobile?.addItem(MediaItem( id: track.id, - album: track.album?.name ?? "", + album: track.album.name, title: track.name, artist: track.artists.asString(), duration: Duration(milliseconds: track.durationMs), artUri: Uri.parse( - (track.album?.images).asUrlString( + (track.album.images).asUrlString( placeholder: ImagePlaceholder.albumArt, ), ), diff --git a/pubspec.lock b/pubspec.lock index f7798e5f..b93ea651 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -98,10 +98,10 @@ packages: dependency: "direct main" description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" audio_service: dependency: "direct main" description: @@ -654,10 +654,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" fast_noise: dependency: transitive description: @@ -1463,10 +1463,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -2104,10 +2104,10 @@ packages: dependency: "direct main" description: name: shadcn_flutter - sha256: "8635e8e0cd2e0fba0a3093a53fbe3bdd37d90d038c4c66d761728d7cfcf23ce3" + sha256: "979a86e203eb1fb139e8b5c84b49b17b28808804cbff189b43052d56ba6854b5" url: "https://pub.dev" source: hosted - version: "0.0.34" + version: "0.0.37" shared_preferences: dependency: "direct main" description: @@ -2232,10 +2232,10 @@ packages: dependency: "direct main" description: name: skeletonizer - sha256: "3b202e4fa9c49b017d368fb0e570d4952bcd19972b67b2face071bdd68abbfae" + sha256: eebc03dc86b298e2d7f61e0ebce5713e9dbbc3e786f825909b4591756f196eb6 url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "2.1.0+1" sky_engine: dependency: transitive description: flutter @@ -2717,10 +2717,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" watcher: dependency: transitive description: @@ -2757,10 +2757,10 @@ packages: dependency: transitive description: name: webdriver - sha256: "3d773670966f02a646319410766d3b5e1037efb7f07cc68f844d5e06cd4d61c8" + sha256: "2f3a14ca026957870cfd9c635b83507e0e51d8091568e90129fbf805aba7cade" url: "https://pub.dev" source: hosted - version: "3.0.4" + version: "3.1.0" webkit_inspection_protocol: dependency: transitive description: @@ -2844,4 +2844,4 @@ packages: version: "1.0.0" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.29.0" + flutter: ">=3.32.0" diff --git a/pubspec.yaml b/pubspec.yaml index a791605a..9c43298e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -102,13 +102,13 @@ dependencies: ref: dart-3-support url: https://github.com/KRTirtho/scrobblenaut.git scroll_to_index: ^3.0.1 - shadcn_flutter: ^0.0.34 + shadcn_flutter: ^0.0.37 shared_preferences: ^2.2.3 shelf: ^1.4.1 shelf_router: ^1.1.4 shelf_web_socket: ^2.0.0 simple_icons: ^10.1.3 - skeletonizer: ^1.1.1 + skeletonizer: ^2.1.0+1 sliding_up_panel: ^2.0.0+1 sliver_tools: ^0.2.12 smtc_windows: ^1.0.0 From f23a078b64df811fbfdb93a56e280d22ced53e02 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 16 Jul 2025 14:43:32 +0600 Subject: [PATCH 37/60] chore: fix spotube logo color on light mode --- lib/modules/root/sidebar/sidebar.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/modules/root/sidebar/sidebar.dart b/lib/modules/root/sidebar/sidebar.dart index 6b61c70b..74ff4a7f 100644 --- a/lib/modules/root/sidebar/sidebar.dart +++ b/lib/modules/root/sidebar/sidebar.dart @@ -35,6 +35,7 @@ class Sidebar extends HookConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { + final ThemeData(:colorScheme) = Theme.of(context); final mediaQuery = MediaQuery.of(context); final layoutMode = @@ -66,13 +67,14 @@ class Sidebar extends HookConsumerWidget { final navigationButtons = [ NavigationLabel( child: mediaQuery.lgAndUp - ? const DefaultTextStyle( + ? DefaultTextStyle( style: TextStyle( fontFamily: "Cookie", fontSize: 30, letterSpacing: 1.8, + color: colorScheme.foreground, ), - child: Text("Spotube"), + child: const Text("Spotube"), ) : const Text(""), ), From 90f9cc28eba8dde97934ce987334f6533f75c10b Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Wed, 16 Jul 2025 22:34:59 +0600 Subject: [PATCH 38/60] feat: enhance image handling --- lib/components/track_tile/track_tile.dart | 15 +++--- lib/models/metadata/image.dart | 56 ++++++++++++++++++---- lib/modules/album/album_card.dart | 50 ++++++++++++++----- lib/modules/playlist/playlist_card.dart | 58 +++++++++++++++++------ lib/modules/root/bottom_player.dart | 2 +- lib/pages/home/home.dart | 2 +- lib/pages/root/root_app.dart | 15 ++++-- 7 files changed, 151 insertions(+), 47 deletions(-) diff --git a/lib/components/track_tile/track_tile.dart b/lib/components/track_tile/track_tile.dart index a3207353..41def4b0 100644 --- a/lib/components/track_tile/track_tile.dart +++ b/lib/components/track_tile/track_tile.dart @@ -5,7 +5,7 @@ import 'package:flutter/gestures.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/spotube_icons.dart'; @@ -71,6 +71,13 @@ class TrackTile extends HookConsumerWidget { final isSelected = isPlaying || isLoading.value; + final imageProvider = useMemoized( + () => UniversalImage.imageProvider( + (track.album.images).smallest(ImagePlaceholder.albumArt), + ), + [track.album.images], + ); + return LayoutBuilder(builder: (context, constrains) { return Listener( onPointerDown: (event) { @@ -147,11 +154,7 @@ class TrackTile extends HookConsumerWidget { borderRadius: theme.borderRadiusMd, image: DecorationImage( fit: BoxFit.cover, - image: UniversalImage.imageProvider( - (track.album.images).asUrlString( - placeholder: ImagePlaceholder.albumArt, - ), - ), + image: imageProvider, ), ), ), diff --git a/lib/models/metadata/image.dart b/lib/models/metadata/image.dart index 6027c3aa..0467dfd6 100644 --- a/lib/models/metadata/image.dart +++ b/lib/models/metadata/image.dart @@ -19,25 +19,65 @@ enum ImagePlaceholder { online, } +final placeholderUrlMap = { + ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, + ImagePlaceholder.artist: Assets.userPlaceholder.path, + ImagePlaceholder.collection: Assets.placeholder.path, + ImagePlaceholder.online: + "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", +}; + extension SpotubeImageExtensions on List? { + /// Returns the URL of the image at the specified index. String asUrlString({ int index = 1, required ImagePlaceholder placeholder, }) { - final String placeholderUrl = { - ImagePlaceholder.albumArt: Assets.albumPlaceholder.path, - ImagePlaceholder.artist: Assets.userPlaceholder.path, - ImagePlaceholder.collection: Assets.placeholder.path, - ImagePlaceholder.online: - "https://avatars.dicebear.com/api/bottts/${PrimitiveUtils.uuid.v4()}.png", - }[placeholder]!; - final sortedImage = this?.sorted((a, b) => a.width!.compareTo(b.width!)); return sortedImage != null && sortedImage.isNotEmpty ? sortedImage[ index > sortedImage.length - 1 ? sortedImage.length - 1 : index] .url + : placeholderUrlMap[placeholder]!; + } + + String smallest(ImagePlaceholder placeholder) { + final sortedImage = this?.sorted((a, b) { + final widthComparison = (a.width ?? 0).compareTo(b.width ?? 0); + if (widthComparison != 0) return widthComparison; + return (a.height ?? 0).compareTo(b.height ?? 0); + }); + + return sortedImage != null && sortedImage.isNotEmpty + ? sortedImage.first.url + : placeholderUrlMap[placeholder]!; + } + + String from200PxTo300PxOrSmallestImage([ + ImagePlaceholder placeholder = ImagePlaceholder.albumArt, + ]) { + final placeholderUrl = placeholderUrlMap[placeholder]!; + + // Sort images by width and height to find the smallest one + final sortedImage = this?.sorted((a, b) { + final widthComparison = (a.width ?? 0).compareTo(b.width ?? 0); + if (widthComparison != 0) return widthComparison; + return (a.height ?? 0).compareTo(b.height ?? 0); + }); + + return sortedImage != null && sortedImage.isNotEmpty + ? sortedImage.firstWhere( + (image) { + final width = image.width ?? 0; + final height = image.height ?? 0; + return width >= 200 && + height >= 200 && + width <= 300 && + height <= 300; + }, + orElse: () => sortedImage.first, + ).url : placeholderUrl; } } diff --git a/lib/modules/album/album_card.dart b/lib/modules/album/album_card.dart index b4809aed..80dfd55b 100644 --- a/lib/modules/album/album_card.dart +++ b/lib/modules/album/album_card.dart @@ -42,32 +42,36 @@ class AlbumCard extends HookConsumerWidget { final historyNotifier = ref.read(playbackHistoryActionsProvider); final isFetchingActiveTrack = ref.watch(queryingTrackInfoProvider); - bool isPlaylistPlaying = useMemoized( + final isPlaylistPlaying = useMemoized( () => playlist.containsCollection(album.id), [playlist, album.id], ); final updating = useState(false); - Future> fetchAllTrack() async { + final fetchAllTrack = useCallback(() async { await ref.read(metadataPluginAlbumTracksProvider(album.id).future); return ref .read(metadataPluginAlbumTracksProvider(album.id).notifier) .fetchAll(); - } + }, [album.id, ref]); - var imageUrl = album.images.asUrlString( - placeholder: ImagePlaceholder.collection, + final imageUrl = useMemoized( + () => album.images.from200PxTo300PxOrSmallestImage( + ImagePlaceholder.collection, + ), + [album.images], ); - var isLoading = + + final isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; - var description = "${album.albumType.name} • ${album.artists.asString()}"; + final description = "${album.albumType.name} • ${album.artists.asString()}"; - void onTap() { + final onTap = useCallback(() { context.navigateTo(AlbumRoute(id: album.id, album: album)); - } + }, [context, album]); - void onPlaybuttonPressed() async { + final onPlaybuttonPressed = useCallback(() async { updating.value = true; try { if (isPlaylistPlaying) { @@ -96,9 +100,20 @@ class AlbumCard extends HookConsumerWidget { } finally { updating.value = false; } - } + }, [ + isPlaylistPlaying, + playing, + audioPlayer, + fetchAllTrack, + context, + ref, + playlistNotifier, + album, + historyNotifier, + updating + ]); - void onAddToQueuePressed() async { + final onAddToQueuePressed = useCallback(() async { if (isPlaylistPlaying) { return; } @@ -135,7 +150,16 @@ class AlbumCard extends HookConsumerWidget { } finally { updating.value = false; } - } + }, [ + isPlaylistPlaying, + updating.value, + fetchAllTrack, + playlistNotifier, + album.id, + historyNotifier, + album, + context + ]); if (_isTile) { return PlaybuttonTile( diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index 811b9332..71015bac 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/components/dialogs/select_device_dialog.dart'; import 'package:spotube/components/playbutton_view/playbutton_card.dart'; @@ -41,7 +41,8 @@ class PlaylistCard extends HookConsumerWidget { final playing = useStream(audioPlayer.playingStream).data ?? audioPlayer.isPlaying; - bool isPlaylistPlaying = useMemoized( + + final isPlaylistPlaying = useMemoized( () => playlistQueue.containsCollection(playlist.id), [playlistQueue, playlist.id], ); @@ -49,7 +50,7 @@ class PlaylistCard extends HookConsumerWidget { final updating = useState(false); final me = ref.watch(metadataPluginUserProvider); - Future> fetchInitialTracks() async { + final fetchInitialTracks = useCallback(() async { if (playlist.id == 'user-liked-tracks') { final tracks = await ref.read(metadataPluginSavedTracksProvider.future); return tracks.items; @@ -59,9 +60,9 @@ class PlaylistCard extends HookConsumerWidget { .read(metadataPluginPlaylistTracksProvider(playlist.id).future); return result.items; - } + }, [playlist.id, ref]); - Future> fetchAllTracks() async { + final fetchAllTracks = useCallback(() async { await fetchInitialTracks(); if (playlist.id == 'user-liked-tracks') { @@ -71,13 +72,13 @@ class PlaylistCard extends HookConsumerWidget { return ref .read(metadataPluginPlaylistTracksProvider(playlist.id).notifier) .fetchAll(); - } + }, [playlist.id, ref, fetchInitialTracks]); - void onTap() { + final onTap = useCallback(() { context.navigateTo(PlaylistRoute(id: playlist.id, playlist: playlist)); - } + }, [context, playlist]); - void onPlaybuttonPressed() async { + final onPlaybuttonPressed = useCallback(() async { try { updating.value = true; if (isPlaylistPlaying && playing) { @@ -97,7 +98,7 @@ class PlaylistCard extends HookConsumerWidget { final allTracks = await fetchAllTracks(); await remotePlayback.load( WebSocketLoadEventData.playlist( - tracks: allTracks as List, + tracks: allTracks, collection: playlist, ), ); @@ -116,9 +117,23 @@ class PlaylistCard extends HookConsumerWidget { updating.value = false; } } - } + }, [ + isPlaylistPlaying, + playing, + fetchInitialTracks, + context, + showSelectDeviceDialog, + ref, + connectProvider, + fetchAllTracks, + playlistNotifier, + playlist.id, + historyNotifier, + playlist, + updating + ]); - void onAddToQueuePressed() async { + final onAddToQueuePressed = useCallback(() async { updating.value = true; try { if (isPlaylistPlaying) return; @@ -155,11 +170,24 @@ class PlaylistCard extends HookConsumerWidget { } finally { updating.value = false; } - } + }, [ + isPlaylistPlaying, + fetchAllTracks, + playlistNotifier, + playlist.id, + historyNotifier, + playlist, + context, + updating + ]); - final imageUrl = playlist.images.asUrlString( - placeholder: ImagePlaceholder.collection, + final imageUrl = useMemoized( + () => playlist.images.from200PxTo300PxOrSmallestImage( + ImagePlaceholder.collection, + ), + [playlist.images], ); + final isLoading = (isPlaylistPlaying && isFetchingActiveTrack) || updating.value; final isOwner = playlist.owner.id == me.asData?.value?.id && diff --git a/lib/modules/root/bottom_player.dart b/lib/modules/root/bottom_player.dart index 8af5d433..5d9fd35b 100644 --- a/lib/modules/root/bottom_player.dart +++ b/lib/modules/root/bottom_player.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; -import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer; +import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/assets.gen.dart'; diff --git a/lib/pages/home/home.dart b/lib/pages/home/home.dart index d766fd2a..89f12f45 100644 --- a/lib/pages/home/home.dart +++ b/lib/pages/home/home.dart @@ -85,7 +85,7 @@ class HomePage extends HookConsumerWidget { }; }, ), - const HomePageBrowseSection(), + const SliverSafeArea(sliver: HomePageBrowseSection()), ], ), )); diff --git a/lib/pages/root/root_app.dart b/lib/pages/root/root_app.dart index 65b97d4f..4cd02881 100644 --- a/lib/pages/root/root_app.dart +++ b/lib/pages/root/root_app.dart @@ -3,6 +3,7 @@ import 'package:flutter/services.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/hooks/configurators/use_check_yt_dlp_installed.dart'; import 'package:spotube/modules/root/bottom_player.dart'; import 'package:spotube/modules/root/sidebar/sidebar.dart'; @@ -43,15 +44,23 @@ class RootAppPage extends HookConsumerWidget { final scaffold = MediaQuery.removeViewInsets( context: context, removeBottom: true, - child: const SafeArea( + child: SafeArea( top: false, child: Scaffold( - footers: [ + footers: const [ BottomPlayer(), SpotubeNavigationBar(), ], floatingFooter: true, - child: Sidebar(child: AutoRouter()), + child: Sidebar( + child: MediaQuery( + data: MediaQuery.of(context).copyWith( + padding: MediaQuery.paddingOf(context) + .copyWith(bottom: 100 * context.theme.scaling), + ), + child: const AutoRouter(), + ), + ), ), ), ); From e83a4bb388494851fd756811a7089e7ea622e01f Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Thu, 17 Jul 2025 01:02:04 +0600 Subject: [PATCH 39/60] feat: add support for automatic plugin repository from github and codeberg --- lib/collections/routes.dart | 2 +- lib/collections/spotube_icons.dart | 2 + .../dialogs/playlist_add_track_dialog.dart | 2 +- .../fallbacks/anonymous_fallback.dart | 2 +- lib/components/heart_button/heart_button.dart | 4 +- .../use_is_user_playlist.dart | 2 +- lib/components/track_tile/track_options.dart | 4 +- lib/models/metadata/metadata.dart | 1 + lib/models/metadata/metadata.freezed.dart | 213 ++++++++++++++++ lib/models/metadata/metadata.g.dart | 18 ++ lib/models/metadata/repository.dart | 14 ++ lib/modules/home/sections/new_releases.dart | 2 +- lib/modules/player/player.dart | 2 +- lib/modules/player/player_actions.dart | 2 +- lib/modules/playlist/playlist_card.dart | 2 +- lib/modules/root/sidebar/sidebar_footer.dart | 4 +- lib/pages/artist/section/header.dart | 2 +- lib/pages/library/user_albums.dart | 2 +- lib/pages/library/user_artists.dart | 2 +- lib/pages/library/user_playlists.dart | 4 +- lib/pages/profile/profile.dart | 2 +- lib/pages/search/search.dart | 2 +- lib/pages/settings/metadata_plugins.dart | 238 ++++++++++++++---- .../metadata_plugin/{ => core}/auth.dart | 0 .../metadata_plugin/core/repositories.dart | 89 +++++++ .../metadata_plugin/{ => core}/user.dart | 2 +- .../metadata_plugin_provider.dart | 34 ++- .../metadata_plugin/playlist/playlist.dart | 2 +- .../metadata_plugin/utils/paginated.dart | 6 +- 29 files changed, 580 insertions(+), 81 deletions(-) create mode 100644 lib/models/metadata/repository.dart rename lib/provider/metadata_plugin/{ => core}/auth.dart (100%) create mode 100644 lib/provider/metadata_plugin/core/repositories.dart rename lib/provider/metadata_plugin/{ => core}/user.dart (88%) diff --git a/lib/collections/routes.dart b/lib/collections/routes.dart index 184a051b..d38303a7 100644 --- a/lib/collections/routes.dart +++ b/lib/collections/routes.dart @@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/collections/routes.gr.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; final rootNavigatorKey = GlobalKey(); diff --git a/lib/collections/spotube_icons.dart b/lib/collections/spotube_icons.dart index 9dd0bec2..b10ef7e3 100644 --- a/lib/collections/spotube_icons.dart +++ b/lib/collections/spotube_icons.dart @@ -137,4 +137,6 @@ abstract class SpotubeIcons { static const extensions = FeatherIcons.package; static const message = FeatherIcons.send; static const upload = FeatherIcons.uploadCloud; + static const plugin = Icons.extension_outlined; + static const warning = FeatherIcons.alertTriangle; } diff --git a/lib/components/dialogs/playlist_add_track_dialog.dart b/lib/components/dialogs/playlist_add_track_dialog.dart index 8bdd24bd..09d831ea 100644 --- a/lib/components/dialogs/playlist_add_track_dialog.dart +++ b/lib/components/dialogs/playlist_add_track_dialog.dart @@ -7,7 +7,7 @@ import 'package:spotube/modules/playlist/playlist_create_dialog.dart'; import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; class PlaylistAddTrackDialog extends HookConsumerWidget { /// The id of the playlist this dialog was opened from diff --git a/lib/components/fallbacks/anonymous_fallback.dart b/lib/components/fallbacks/anonymous_fallback.dart index ed769959..cb6028a7 100644 --- a/lib/components/fallbacks/anonymous_fallback.dart +++ b/lib/components/fallbacks/anonymous_fallback.dart @@ -5,7 +5,7 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/utils/platform.dart'; diff --git a/lib/components/heart_button/heart_button.dart b/lib/components/heart_button/heart_button.dart index af5bbd78..eca3c513 100644 --- a/lib/components/heart_button/heart_button.dart +++ b/lib/components/heart_button/heart_button.dart @@ -4,9 +4,9 @@ import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:spotube/components/heart_button/use_track_toggle_like.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; class HeartButton extends HookConsumerWidget { final bool isLiked; diff --git a/lib/components/track_presentation/use_is_user_playlist.dart b/lib/components/track_presentation/use_is_user_playlist.dart index 18426118..8792f6e7 100644 --- a/lib/components/track_presentation/use_is_user_playlist.dart +++ b/lib/components/track_presentation/use_is_user_playlist.dart @@ -1,7 +1,7 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; bool useIsUserPlaylist(WidgetRef ref, String playlistId) { final userPlaylistsQuery = ref.watch(metadataPluginSavedPlaylistsProvider); diff --git a/lib/components/track_tile/track_options.dart b/lib/components/track_tile/track_options.dart index 03c0244d..09f361c2 100644 --- a/lib/components/track_tile/track_options.dart +++ b/lib/components/track_tile/track_options.dart @@ -26,11 +26,11 @@ import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:spotube/services/metadata/endpoints/error.dart'; import 'package:url_launcher/url_launcher_string.dart'; diff --git a/lib/models/metadata/metadata.dart b/lib/models/metadata/metadata.dart index 7e3496a4..97da704c 100644 --- a/lib/models/metadata/metadata.dart +++ b/lib/models/metadata/metadata.dart @@ -27,3 +27,4 @@ part 'track.dart'; part 'user.dart'; part 'plugin.dart'; +part 'repository.dart'; diff --git a/lib/models/metadata/metadata.freezed.dart b/lib/models/metadata/metadata.freezed.dart index 89361ff6..c6a3609a 100644 --- a/lib/models/metadata/metadata.freezed.dart +++ b/lib/models/metadata/metadata.freezed.dart @@ -4815,3 +4815,216 @@ abstract class _PluginConfiguration extends PluginConfiguration { _$$PluginConfigurationImplCopyWith<_$PluginConfigurationImpl> get copyWith => throw _privateConstructorUsedError; } + +MetadataPluginRepository _$MetadataPluginRepositoryFromJson( + Map json) { + return _MetadataPluginRepository.fromJson(json); +} + +/// @nodoc +mixin _$MetadataPluginRepository { + String get name => throw _privateConstructorUsedError; + String get owner => throw _privateConstructorUsedError; + String get description => throw _privateConstructorUsedError; + String get repoUrl => throw _privateConstructorUsedError; + + /// Serializes this MetadataPluginRepository to a JSON map. + Map toJson() => throw _privateConstructorUsedError; + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + $MetadataPluginRepositoryCopyWith get copyWith => + throw _privateConstructorUsedError; +} + +/// @nodoc +abstract class $MetadataPluginRepositoryCopyWith<$Res> { + factory $MetadataPluginRepositoryCopyWith(MetadataPluginRepository value, + $Res Function(MetadataPluginRepository) then) = + _$MetadataPluginRepositoryCopyWithImpl<$Res, MetadataPluginRepository>; + @useResult + $Res call({String name, String owner, String description, String repoUrl}); +} + +/// @nodoc +class _$MetadataPluginRepositoryCopyWithImpl<$Res, + $Val extends MetadataPluginRepository> + implements $MetadataPluginRepositoryCopyWith<$Res> { + _$MetadataPluginRepositoryCopyWithImpl(this._value, this._then); + + // ignore: unused_field + final $Val _value; + // ignore: unused_field + final $Res Function($Val) _then; + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? owner = null, + Object? description = null, + Object? repoUrl = null, + }) { + return _then(_value.copyWith( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + repoUrl: null == repoUrl + ? _value.repoUrl + : repoUrl // ignore: cast_nullable_to_non_nullable + as String, + ) as $Val); + } +} + +/// @nodoc +abstract class _$$MetadataPluginRepositoryImplCopyWith<$Res> + implements $MetadataPluginRepositoryCopyWith<$Res> { + factory _$$MetadataPluginRepositoryImplCopyWith( + _$MetadataPluginRepositoryImpl value, + $Res Function(_$MetadataPluginRepositoryImpl) then) = + __$$MetadataPluginRepositoryImplCopyWithImpl<$Res>; + @override + @useResult + $Res call({String name, String owner, String description, String repoUrl}); +} + +/// @nodoc +class __$$MetadataPluginRepositoryImplCopyWithImpl<$Res> + extends _$MetadataPluginRepositoryCopyWithImpl<$Res, + _$MetadataPluginRepositoryImpl> + implements _$$MetadataPluginRepositoryImplCopyWith<$Res> { + __$$MetadataPluginRepositoryImplCopyWithImpl( + _$MetadataPluginRepositoryImpl _value, + $Res Function(_$MetadataPluginRepositoryImpl) _then) + : super(_value, _then); + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @pragma('vm:prefer-inline') + @override + $Res call({ + Object? name = null, + Object? owner = null, + Object? description = null, + Object? repoUrl = null, + }) { + return _then(_$MetadataPluginRepositoryImpl( + name: null == name + ? _value.name + : name // ignore: cast_nullable_to_non_nullable + as String, + owner: null == owner + ? _value.owner + : owner // ignore: cast_nullable_to_non_nullable + as String, + description: null == description + ? _value.description + : description // ignore: cast_nullable_to_non_nullable + as String, + repoUrl: null == repoUrl + ? _value.repoUrl + : repoUrl // ignore: cast_nullable_to_non_nullable + as String, + )); + } +} + +/// @nodoc +@JsonSerializable() +class _$MetadataPluginRepositoryImpl implements _MetadataPluginRepository { + _$MetadataPluginRepositoryImpl( + {required this.name, + required this.owner, + required this.description, + required this.repoUrl}); + + factory _$MetadataPluginRepositoryImpl.fromJson(Map json) => + _$$MetadataPluginRepositoryImplFromJson(json); + + @override + final String name; + @override + final String owner; + @override + final String description; + @override + final String repoUrl; + + @override + String toString() { + return 'MetadataPluginRepository(name: $name, owner: $owner, description: $description, repoUrl: $repoUrl)'; + } + + @override + bool operator ==(Object other) { + return identical(this, other) || + (other.runtimeType == runtimeType && + other is _$MetadataPluginRepositoryImpl && + (identical(other.name, name) || other.name == name) && + (identical(other.owner, owner) || other.owner == owner) && + (identical(other.description, description) || + other.description == description) && + (identical(other.repoUrl, repoUrl) || other.repoUrl == repoUrl)); + } + + @JsonKey(includeFromJson: false, includeToJson: false) + @override + int get hashCode => + Object.hash(runtimeType, name, owner, description, repoUrl); + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) + @override + @pragma('vm:prefer-inline') + _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> + get copyWith => __$$MetadataPluginRepositoryImplCopyWithImpl< + _$MetadataPluginRepositoryImpl>(this, _$identity); + + @override + Map toJson() { + return _$$MetadataPluginRepositoryImplToJson( + this, + ); + } +} + +abstract class _MetadataPluginRepository implements MetadataPluginRepository { + factory _MetadataPluginRepository( + {required final String name, + required final String owner, + required final String description, + required final String repoUrl}) = _$MetadataPluginRepositoryImpl; + + factory _MetadataPluginRepository.fromJson(Map json) = + _$MetadataPluginRepositoryImpl.fromJson; + + @override + String get name; + @override + String get owner; + @override + String get description; + @override + String get repoUrl; + + /// Create a copy of MetadataPluginRepository + /// with the given fields replaced by the non-null parameter values. + @override + @JsonKey(includeFromJson: false, includeToJson: false) + _$$MetadataPluginRepositoryImplCopyWith<_$MetadataPluginRepositoryImpl> + get copyWith => throw _privateConstructorUsedError; +} diff --git a/lib/models/metadata/metadata.g.dart b/lib/models/metadata/metadata.g.dart index ffafa931..73d42247 100644 --- a/lib/models/metadata/metadata.g.dart +++ b/lib/models/metadata/metadata.g.dart @@ -462,3 +462,21 @@ const _$PluginApisEnumMap = { const _$PluginAbilitiesEnumMap = { PluginAbilities.authentication: 'authentication', }; + +_$MetadataPluginRepositoryImpl _$$MetadataPluginRepositoryImplFromJson( + Map json) => + _$MetadataPluginRepositoryImpl( + name: json['name'] as String, + owner: json['owner'] as String, + description: json['description'] as String, + repoUrl: json['repoUrl'] as String, + ); + +Map _$$MetadataPluginRepositoryImplToJson( + _$MetadataPluginRepositoryImpl instance) => + { + 'name': instance.name, + 'owner': instance.owner, + 'description': instance.description, + 'repoUrl': instance.repoUrl, + }; diff --git a/lib/models/metadata/repository.dart b/lib/models/metadata/repository.dart new file mode 100644 index 00000000..06151dee --- /dev/null +++ b/lib/models/metadata/repository.dart @@ -0,0 +1,14 @@ +part of './metadata.dart'; + +@freezed +class MetadataPluginRepository with _$MetadataPluginRepository { + factory MetadataPluginRepository({ + required String name, + required String owner, + required String description, + required String repoUrl, + }) = _MetadataPluginRepository; + + factory MetadataPluginRepository.fromJson(Map json) => + _$MetadataPluginRepositoryFromJson(json); +} diff --git a/lib/modules/home/sections/new_releases.dart b/lib/modules/home/sections/new_releases.dart index e916ae4f..b2f46b10 100644 --- a/lib/modules/home/sections/new_releases.dart +++ b/lib/modules/home/sections/new_releases.dart @@ -4,7 +4,7 @@ import 'package:spotube/components/horizontal_playbutton_card_view/horizontal_pl import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/album/releases.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; class HomeNewReleasesSection extends HookConsumerWidget { diff --git a/lib/modules/player/player.dart b/lib/modules/player/player.dart index 1dcd7de1..f96c52df 100644 --- a/lib/modules/player/player.dart +++ b/lib/modules/player/player.dart @@ -21,7 +21,7 @@ import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/modules/root/spotube_navigation_bar.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/server/active_track_sources.dart'; import 'package:spotube/provider/volume_provider.dart'; import 'package:spotube/services/sourced_track/sources/youtube.dart'; diff --git a/lib/modules/player/player_actions.dart b/lib/modules/player/player_actions.dart index e01b6926..4e3de7e0 100644 --- a/lib/modules/player/player_actions.dart +++ b/lib/modules/player/player_actions.dart @@ -18,7 +18,7 @@ import 'package:spotube/extensions/duration.dart'; import 'package:spotube/provider/download_manager_provider.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/local_tracks/local_tracks_provider.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/sleep_timer_provider.dart'; class PlayerActions extends HookConsumerWidget { diff --git a/lib/modules/playlist/playlist_card.dart b/lib/modules/playlist/playlist_card.dart index 71015bac..1d221a33 100644 --- a/lib/modules/playlist/playlist_card.dart +++ b/lib/modules/playlist/playlist_card.dart @@ -15,7 +15,7 @@ import 'package:spotube/provider/history/history.dart'; import 'package:spotube/provider/audio_player/audio_player.dart'; import 'package:spotube/provider/metadata_plugin/library/tracks.dart'; import 'package:spotube/provider/metadata_plugin/tracks/playlist.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:spotube/services/audio_player/audio_player.dart'; class PlaylistCard extends HookConsumerWidget { diff --git a/lib/modules/root/sidebar/sidebar_footer.dart b/lib/modules/root/sidebar/sidebar_footer.dart index 6ff3ab23..4c46c13b 100644 --- a/lib/modules/root/sidebar/sidebar_footer.dart +++ b/lib/modules/root/sidebar/sidebar_footer.dart @@ -11,8 +11,8 @@ import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/modules/connect/connect_device.dart'; import 'package:spotube/provider/download_manager_provider.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; class SidebarFooter extends HookConsumerWidget implements NavigationBarItem { const SidebarFooter({ diff --git a/lib/pages/artist/section/header.dart b/lib/pages/artist/section/header.dart index 2ce9178c..b8e7e5dc 100644 --- a/lib/pages/artist/section/header.dart +++ b/lib/pages/artist/section/header.dart @@ -12,7 +12,7 @@ import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/blacklist_provider.dart'; import 'package:spotube/provider/metadata_plugin/artist/artist.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:spotube/utils/primitive_utils.dart'; diff --git a/lib/pages/library/user_albums.dart b/lib/pages/library/user_albums.dart index 3494211e..42c6af7c 100644 --- a/lib/pages/library/user_albums.dart +++ b/lib/pages/library/user_albums.dart @@ -14,7 +14,7 @@ import 'package:spotube/modules/album/album_card.dart'; import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/albums.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/library/user_artists.dart b/lib/pages/library/user_artists.dart index 6087f41c..097dff4f 100644 --- a/lib/pages/library/user_artists.dart +++ b/lib/pages/library/user_artists.dart @@ -17,7 +17,7 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/waypoint.dart'; import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/artists.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/library/user_playlists.dart b/lib/pages/library/user_playlists.dart index 16bd7882..c7493ec3 100644 --- a/lib/pages/library/user_playlists.dart +++ b/lib/pages/library/user_playlists.dart @@ -15,9 +15,9 @@ import 'package:spotube/components/inter_scrollbar/inter_scrollbar.dart'; import 'package:spotube/components/fallbacks/anonymous_fallback.dart'; import 'package:spotube/modules/playlist/playlist_card.dart'; import 'package:spotube/extensions/context.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:auto_route/auto_route.dart'; @RoutePage() diff --git a/lib/pages/profile/profile.dart b/lib/pages/profile/profile.dart index 45d34117..eb3dec2a 100644 --- a/lib/pages/profile/profile.dart +++ b/lib/pages/profile/profile.dart @@ -8,7 +8,7 @@ import 'package:spotube/components/image/universal_image.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/extensions/context.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/user.dart'; +import 'package:spotube/provider/metadata_plugin/core/user.dart'; import 'package:url_launcher/url_launcher_string.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/search/search.dart b/lib/pages/search/search.dart index 0db34810..7dec4b04 100644 --- a/lib/pages/search/search.dart +++ b/lib/pages/search/search.dart @@ -17,7 +17,7 @@ import 'package:spotube/pages/search/tabs/all.dart'; import 'package:spotube/pages/search/tabs/artists.dart'; import 'package:spotube/pages/search/tabs/playlists.dart'; import 'package:spotube/pages/search/tabs/tracks.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/search/all.dart'; import 'package:spotube/services/kv_store/kv_store.dart'; import 'package:auto_route/auto_route.dart'; diff --git a/lib/pages/settings/metadata_plugins.dart b/lib/pages/settings/metadata_plugins.dart index 2de1c36c..4db82e73 100644 --- a/lib/pages/settings/metadata_plugins.dart +++ b/lib/pages/settings/metadata_plugins.dart @@ -4,14 +4,20 @@ import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart'; +import 'package:shadcn_flutter/shadcn_flutter_extension.dart'; +import 'package:skeletonizer/skeletonizer.dart'; import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/components/form/text_form_field.dart'; import 'package:spotube/components/titlebar/titlebar.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/repositories.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:file_picker/file_picker.dart'; +import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/utils/platform.dart'; +import 'package:url_launcher/url_launcher_string.dart'; +import 'package:very_good_infinite_list/very_good_infinite_list.dart'; @RoutePage() class SettingsMetadataProviderPage extends HookConsumerWidget { @@ -26,6 +32,11 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { final metadataPlugin = ref.watch(metadataPluginProvider); final isAuthenticated = ref.watch(metadataPluginAuthenticatedProvider); + final pluginReposSnapshot = ref.watch(metadataPluginRepositoriesProvider); + final pluginReposNotifier = + ref.watch(metadataPluginRepositoriesProvider.notifier); + final pluginRepos = pluginReposSnapshot.asData?.value.items ?? []; + return Scaffold( headers: const [ TitleBar( @@ -103,76 +114,203 @@ class SettingsMetadataProviderPage extends HookConsumerWidget { ], ), ), + const SliverGap(12), + SliverToBoxAdapter( + child: Row( + children: [ + const Gap(8), + const Text("Installed").h4, + const Gap(8), + const Expanded(child: Divider()), + const Gap(8), + ], + ), + ), const SliverGap(20), SliverList.separated( itemCount: plugins.asData?.value.plugins.length ?? 0, - separatorBuilder: (context, index) => const Divider(), + separatorBuilder: (context, index) => const Gap(12), itemBuilder: (context, index) { final plugin = plugins.asData!.value.plugins[index]; final isDefault = plugins.asData!.value.defaultPlugin == index; final requiresAuth = isDefault && plugin.abilities.contains(PluginAbilities.authentication); + return Card( child: Column( - spacing: 8, - mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisAlignment: MainAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 12, children: [ - Basic( - title: Text(plugin.name), - subtitle: Text(plugin.description), - trailing: Row( - spacing: 8, - children: [ - Button.primary( - enabled: !isDefault, - onPressed: () async { - await pluginsNotifier.setDefaultPlugin(plugin); - }, - child: isDefault - ? const Text("Default") - : const Text("Make default"), - ), - IconButton.destructive( + FutureBuilder( + future: pluginsNotifier.getLogoPath(plugin), + builder: (context, snapshot) { + return Basic( + leading: snapshot.hasData + ? Image.file( + snapshot.data!, + width: 36, + height: 36, + ) + : Container( + height: 36, + width: 36, + alignment: Alignment.center, + decoration: BoxDecoration( + color: + context.theme.colorScheme.secondary, + borderRadius: BorderRadius.circular(8), + ), + child: const Icon(SpotubeIcons.plugin), + ), + title: Text(plugin.name), + subtitle: Text(plugin.description), + trailing: IconButton.ghost( onPressed: () async { await pluginsNotifier.removePlugin(plugin); }, - icon: const Icon(SpotubeIcons.trash), - ), - ], - ), - ), - if (requiresAuth) - Row( - children: [ - const Text("Plugin requires authentication"), - const Spacer(), - if (isAuthenticated.asData?.value != true) - Button.primary( - onPressed: () async { - await metadataPlugin.asData?.value?.auth - .authenticate(); - }, - leading: const Icon(SpotubeIcons.login), - child: const Text("Login"), - ) - else - Button.destructive( - onPressed: () async { - await metadataPlugin.asData?.value?.auth - .logout(); - }, - leading: const Icon(SpotubeIcons.logout), - child: const Text("Logout"), + icon: const Icon( + SpotubeIcons.trash, + color: Colors.red, ), - ], - ) + ), + ); + }, + ), + if (plugin.abilities + .contains(PluginAbilities.authentication) && + isDefault) + Container( + decoration: BoxDecoration( + color: context.theme.colorScheme.secondary, + borderRadius: BorderRadius.circular(8), + ), + padding: const EdgeInsets.all(12), + child: const Row( + spacing: 8, + children: [ + Icon(SpotubeIcons.warning, color: Colors.yellow), + Text("Plugin requires authentication"), + ], + ), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Button.secondary( + enabled: !isDefault, + onPressed: () async { + await pluginsNotifier.setDefaultPlugin(plugin); + }, + child: isDefault + ? const Text("Default") + : const Text("Set default"), + ), + if (isAuthenticated.asData?.value != true && + requiresAuth && + isDefault) + Button.primary( + onPressed: () async { + await metadataPlugin.asData?.value?.auth + .authenticate(); + }, + leading: const Icon(SpotubeIcons.login), + child: const Text("Login"), + ) + else if (isAuthenticated.asData?.value == true && + requiresAuth && + isDefault) + Button.destructive( + onPressed: () async { + await metadataPlugin.asData?.value?.auth + .logout(); + }, + leading: const Icon(SpotubeIcons.logout), + child: const Text("Logout"), + ) + ], + ) ], ), ); }, ), + const SliverGap(12), + SliverToBoxAdapter( + child: Row( + children: [ + const Gap(8), + const Text("Available plugins").h4, + const Gap(8), + const Expanded(child: Divider()), + const Gap(8), + ], + ), + ), + const SliverGap(12), + Skeletonizer.sliver( + enabled: pluginReposSnapshot.isLoading, + child: SliverInfiniteList( + isLoading: pluginReposSnapshot.isLoading && + !pluginReposSnapshot.isLoadingNextPage, + itemCount: pluginRepos.length, + onFetchData: pluginReposNotifier.fetchMore, + itemBuilder: (context, index) { + final pluginRepo = pluginRepos[index]; + final host = Uri.parse(pluginRepo.repoUrl).host; + + return Card( + child: Basic( + title: Text(pluginRepo.name), + subtitle: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8, + children: [ + Text(pluginRepo.description), + Row( + spacing: 8, + children: [ + if (pluginRepo.owner == "KRTirtho") ...[ + const PrimaryBadge( + leading: Icon(SpotubeIcons.done), + child: Text("Official"), + ), + SecondaryBadge( + leading: host == "github.com" + ? const Icon(SpotubeIcons.github) + : null, + child: Text(host), + onPressed: () { + launchUrlString(pluginRepo.repoUrl); + }, + ), + ] else ...[ + Text("Author: ${pluginRepo.owner}"), + const DestructiveBadge( + leading: Icon(SpotubeIcons.warning), + child: Text("Third-party"), + ) + ] + ], + ), + ], + ), + trailing: Button.primary( + onPressed: () async { + final pluginConfig = await pluginsNotifier + .downloadAndCachePlugin(pluginRepo.repoUrl); + + await pluginsNotifier.addPlugin(pluginConfig); + }, + leading: const Icon(SpotubeIcons.add), + child: const Text("Install"), + ), + ), + ); + }, + ), + ), ], ), ), diff --git a/lib/provider/metadata_plugin/auth.dart b/lib/provider/metadata_plugin/core/auth.dart similarity index 100% rename from lib/provider/metadata_plugin/auth.dart rename to lib/provider/metadata_plugin/core/auth.dart diff --git a/lib/provider/metadata_plugin/core/repositories.dart b/lib/provider/metadata_plugin/core/repositories.dart new file mode 100644 index 00000000..55c11ed2 --- /dev/null +++ b/lib/provider/metadata_plugin/core/repositories.dart @@ -0,0 +1,89 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:spotube/models/metadata/metadata.dart'; +import 'package:spotube/provider/metadata_plugin/utils/paginated.dart'; +import 'package:spotube/services/dio/dio.dart'; + +class MetadataPluginRepositoriesNotifier + extends PaginatedAsyncNotifier { + MetadataPluginRepositoriesNotifier() : super(); + + Map _hasMore = {}; + + @override + fetch(int offset, int limit) async { + final gitubSearch = globalDio.get( + "https://api.github.com/search/repositories", + queryParameters: { + "q": "topic:spotube-plugin", + "sort": "stars", + "order": "desc", + "page": offset, + "per_page": limit, + }, + ); + + final codebergSearch = globalDio.get( + "https://codeberg.org/api/v1/repos/search", + queryParameters: { + "q": "spotube-plugin", + "topic": "true", + "sort": "stars", + "order": "desc", + "page": offset, + "limit": limit, + }, + ); + + final responses = await Future.wait([ + if (_hasMore["github.com"] ?? true) gitubSearch, + if (_hasMore["codeberg.org"] ?? true) codebergSearch, + ]); + + final repos = responses + .expand( + (response) => response.data["data"] ?? response.data["items"] ?? [], + ) + .map((repo) { + return MetadataPluginRepository( + name: repo["name"] ?? "", + owner: repo["owner"]["login"] ?? "", + description: repo["description"] ?? "", + repoUrl: repo["html_url"] ?? "", + ); + }).toList(); + + final hasMore = responses.any((response) { + final items = + (response.data["data"] ?? response.data["items"] ?? []) as List; + _hasMore[response.requestOptions.uri.host] = + items.length >= limit && items.isNotEmpty; + + return _hasMore[response.requestOptions.uri.host] ?? false; + }); + + return SpotubePaginationResponseObject( + items: repos, + total: responses.fold( + 0, + (previousValue, response) => previousValue + + (response.data["total_count"] ?? + int.tryParse(response.headers["x-total-count"]?[0] ?? "") ?? + 0) as int, + ), + hasMore: hasMore, + nextOffset: hasMore ? offset + 1 : null, + limit: limit, + ); + } + + @override + build() async { + return await fetch(0, 10); + } +} + +final metadataPluginRepositoriesProvider = AsyncNotifierProvider< + MetadataPluginRepositoriesNotifier, + SpotubePaginationResponseObject>( + () => MetadataPluginRepositoriesNotifier(), +); diff --git a/lib/provider/metadata_plugin/user.dart b/lib/provider/metadata_plugin/core/user.dart similarity index 88% rename from lib/provider/metadata_plugin/user.dart rename to lib/provider/metadata_plugin/core/user.dart index 7dd65766..4acfb8f7 100644 --- a/lib/provider/metadata_plugin/user.dart +++ b/lib/provider/metadata_plugin/core/user.dart @@ -1,6 +1,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; -import 'package:spotube/provider/metadata_plugin/auth.dart'; +import 'package:spotube/provider/metadata_plugin/core/auth.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; final metadataPluginUserProvider = FutureProvider( diff --git a/lib/provider/metadata_plugin/metadata_plugin_provider.dart b/lib/provider/metadata_plugin/metadata_plugin_provider.dart index 81199699..717a81c0 100644 --- a/lib/provider/metadata_plugin/metadata_plugin_provider.dart +++ b/lib/provider/metadata_plugin/metadata_plugin_provider.dart @@ -130,7 +130,11 @@ class MetadataPluginNotifier extends AsyncNotifier { final parsedUri = Uri.parse(repoUrl); final uri = parsedUri.replace( host: "api.github.com", - path: "/repos/${parsedUri.path}/releases", + pathSegments: [ + "repos", + ...parsedUri.pathSegments, + "releases", + ], queryParameters: { "per_page": "1", "page": "1", @@ -143,7 +147,13 @@ class MetadataPluginNotifier extends AsyncNotifier { Uri _getCodebergeReleasesUrl(String repoUrl) { final parsedUri = Uri.parse(repoUrl); final uri = parsedUri.replace( - path: "/api/v1/repos/${parsedUri.path}/releases", + pathSegments: [ + "api", + "v1", + "repos", + ...parsedUri.pathSegments, + "releases", + ], queryParameters: { "limit": "1", "page": "1", @@ -154,6 +164,7 @@ class MetadataPluginNotifier extends AsyncNotifier { } Future _getPluginDownloadUrl(Uri uri) async { + print("Getting plugin download URL from: $uri"); final res = await globalDio.getUri( uri, options: Options(responseType: ResponseType.json), @@ -227,8 +238,7 @@ class MetadataPluginNotifier extends AsyncNotifier { Future downloadAndCachePlugin(String url) async { final res = await globalDio.head(url); final isSupportedWebsite = - (res.headers["Content-Type"] as String?)?.startsWith("text/html") == - true && + (res.headers["Content-Type"]?.first)?.startsWith("text/html") == true && allowedDomainsRegex.hasMatch(url); String pluginDownloadUrl = url; if (isSupportedWebsite) { @@ -329,6 +339,22 @@ class MetadataPluginNotifier extends AsyncNotifier { return await libraryFile.readAsBytes(); } + + Future getLogoPath(PluginConfiguration plugin) async { + final pluginDir = await _getPluginDir(); + final pluginExtractionDirPath = join( + pluginDir.path, + ServiceUtils.sanitizeFilename(plugin.name), + ); + + final logoFile = File(join(pluginExtractionDirPath, "logo.png")); + + if (!logoFile.existsSync()) { + return null; + } + + return logoFile; + } } final metadataPluginsProvider = diff --git a/lib/provider/metadata_plugin/playlist/playlist.dart b/lib/provider/metadata_plugin/playlist/playlist.dart index 8d5b71be..af26673a 100644 --- a/lib/provider/metadata_plugin/playlist/playlist.dart +++ b/lib/provider/metadata_plugin/playlist/playlist.dart @@ -2,7 +2,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/provider/metadata_plugin/library/playlists.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/core/user.dart'; import 'package:spotube/provider/metadata_plugin/utils/common.dart'; import 'package:spotube/services/metadata/endpoints/error.dart'; import 'package:spotube/services/metadata/metadata.dart'; diff --git a/lib/provider/metadata_plugin/utils/paginated.dart b/lib/provider/metadata_plugin/utils/paginated.dart index 6a37929d..e1d2bf26 100644 --- a/lib/provider/metadata_plugin/utils/paginated.dart +++ b/lib/provider/metadata_plugin/utils/paginated.dart @@ -27,8 +27,7 @@ mixin PaginatedAsyncNotifierMixin state.value!.items.isEmpty ? [] : state.value!.items.cast(); final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]) - as SpotubePaginationResponseObject; + return newState.copyWith(items: [...oldItems, ...items]); }, ); } @@ -48,8 +47,7 @@ mixin PaginatedAsyncNotifierMixin hasMore = newState.hasMore; final oldItems = state.items.isEmpty ? [] : state.items.cast(); final items = newState.items.isEmpty ? [] : newState.items.cast(); - return newState.copyWith(items: [...oldItems, ...items]) - as SpotubePaginationResponseObject; + return newState.copyWith(items: [...oldItems, ...items]); }); } From 2f304fa9437d9ce3bf05d6540d6e3d95d4b4e3b4 Mon Sep 17 00:00:00 2001 From: Kingkor Roy Tirtho Date: Fri, 18 Jul 2025 10:34:21 +0600 Subject: [PATCH 40/60] feat: use isolate for youtube_explode engine --- .../youtube_engine/newpipe_engine.dart | 3 + .../youtube_engine/youtube_engine.dart | 2 + .../youtube_explode_engine.dart | 153 +++++++++++++++++- .../youtube_engine/yt_dlp_engine.dart | 3 + 4 files changed, 154 insertions(+), 7 deletions(-) diff --git a/lib/services/youtube_engine/newpipe_engine.dart b/lib/services/youtube_engine/newpipe_engine.dart index f4a87f33..ae451e22 100644 --- a/lib/services/youtube_engine/newpipe_engine.dart +++ b/lib/services/youtube_engine/newpipe_engine.dart @@ -110,4 +110,7 @@ class NewPipeEngine implements YouTubeEngine { return resultsWithVideos; } + + @override + void dispose() {} } diff --git a/lib/services/youtube_engine/youtube_engine.dart b/lib/services/youtube_engine/youtube_engine.dart index 5a8ffa4e..1c4ac7d6 100644 --- a/lib/services/youtube_engine/youtube_engine.dart +++ b/lib/services/youtube_engine/youtube_engine.dart @@ -11,4 +11,6 @@ abstract interface class YouTubeEngine { Future getStreamManifest(String videoId); Future<(Video, StreamManifest)> getVideoWithStreamInfo(String videoId); Future> searchVideos(String query); + + void dispose(); } diff --git a/lib/services/youtube_engine/youtube_explode_engine.dart b/lib/services/youtube_engine/youtube_explode_engine.dart index 15906aad..9eb45f83 100644 --- a/lib/services/youtube_engine/youtube_explode_engine.dart +++ b/lib/services/youtube_engine/youtube_explode_engine.dart @@ -1,8 +1,136 @@ +import 'dart:isolate'; + import 'package:spotube/services/youtube_engine/youtube_engine.dart'; import 'package:youtube_explode_dart/youtube_explode_dart.dart'; +import 'dart:async'; + +/// It contains methods that are computationally expensive +class IsolatedYoutubeExplode { + final Isolate _isolate; + final SendPort _sendPort; + final ReceivePort _receivePort; + + IsolatedYoutubeExplode._( + Isolate isolate, + ReceivePort receivePort, + SendPort sendPort, + ) : _isolate = isolate, + _receivePort = receivePort, + _sendPort = sendPort; + + static IsolatedYoutubeExplode? _instance; + + static IsolatedYoutubeExplode get instance => _instance!; + + static bool get isInitialized => _instance != null; + + static Future initialize() async { + if (_instance != null) { + return; + } + + final completer = Completer(); + + final receivePort = ReceivePort(); + + /// Listen for the main isolate to set the main port + final subscription = receivePort.listen((message) { + if (message is SendPort) { + completer.complete(message); + } + }); + + final isolate = await Isolate.spawn(_isolateEntry, receivePort.sendPort); + + _instance = IsolatedYoutubeExplode._( + isolate, + receivePort, + await completer.future, + ); + + if (completer.isCompleted) { + subscription.cancel(); + } + } + + static void _isolateEntry(SendPort mainSendPort) { + final receivePort = ReceivePort(); + final youtubeExplode = YoutubeExplode(); + + /// Send the main port to the main isolate + mainSendPort.send(receivePort.sendPort); + + receivePort.listen((message) async { + final SendPort replyPort = message[0]; + final String methodName = message[1]; + final List arguments = message[2]; + + // Run the requested method on YoutubeExplode + var result = switch (methodName) { + "search" => youtubeExplode.search + .search( + arguments[0] as String, + filter: arguments.elementAtOrNull(1) ?? TypeFilters.video, + ) + .then((s) => s.toList()), + "video" => youtubeExplode.videos.get(arguments[0] as String), + "manifest" => youtubeExplode.videos.streamsClient.getManifest( + arguments[0] as String, + requireWatchPage: arguments.elementAtOrNull(1) ?? true, + ytClients: arguments.elementAtOrNull(2) as List?, + ), + _ => throw ArgumentError('Invalid method name: $methodName'), + }; + + replyPort.send(await result); + }); + } + + Future _runMethod(String methodName, List args) { + final completer = Completer(); + final responsePort = ReceivePort(); + + responsePort.listen((message) { + completer.complete(message as T); + responsePort.close(); + }); + + _sendPort.send([responsePort.sendPort, methodName, args]); + return completer.future; + } + + Future> search( + String query, { + SearchFilter? filter, + }) async { + return _runMethod>("search", [query]); + } + + Future