fix: downloaded tracks are not tagged with metadata

This commit is contained in:
Kingkor Roy Tirtho 2025-11-08 15:49:37 +06:00
parent 700a69fcd1
commit 3209c75144
17 changed files with 112 additions and 554 deletions

View File

@ -202,7 +202,6 @@ If you are curious, you can [read the reason of choosing this license](https://d
1. [Invidious](https://invidious.io/) - Invidious is an open source alternative front-end to YouTube.
1. [yt-dlp](https://github.com/yt-dlp/yt-dlp) - A feature-rich command-line audio/video downloader.
1. [NewPipeExtractor](https://github.com/TeamNewPipe/NewPipeExtractor) - NewPipe's core library for extracting data from streaming sites.
1. [SongLink](https://song.link) - SongLink is a free smart link service that helps you share music with your audience. It's a one-stop-shop for creating smart links for music, podcasts, and other audio content
1. [LRCLib](https://lrclib.net/) - A public synced lyric API.
1. [Linux](https://www.linux.org) - Linux is a family of open-source Unix-like operating systems based on the Linux kernel, an operating system kernel first released on September 17, 1991, by Linus Torvalds. Linux is typically packaged in a Linux distribution
1. [AUR](https://aur.archlinux.org) - AUR stands for Arch User Repository. It is a community-driven repository for Arch-based Linux distributions users

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -66,6 +66,19 @@ class $AssetsImagesGen {
];
}
class $AssetsPluginsGen {
const $AssetsPluginsGen();
/// Directory path: assets/plugins/spotube-plugin-musicbrainz-listenbrainz
$AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen
get spotubePluginMusicbrainzListenbrainz =>
const $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen();
/// Directory path: assets/plugins/spotube-plugin-youtube-audio
$AssetsPluginsSpotubePluginYoutubeAudioGen get spotubePluginYoutubeAudio =>
const $AssetsPluginsSpotubePluginYoutubeAudioGen();
}
class $AssetsImagesLogosGen {
const $AssetsImagesLogosGen();
@ -81,13 +94,30 @@ class $AssetsImagesLogosGen {
AssetGenImage get jiosaavn =>
const AssetGenImage('assets/images/logos/jiosaavn.png');
/// File path: assets/images/logos/songlink-transparent.png
AssetGenImage get songlinkTransparent =>
const AssetGenImage('assets/images/logos/songlink-transparent.png');
/// List of all assets
List<AssetGenImage> get values => [dabMusic, invidious, jiosaavn];
}
class $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen {
const $AssetsPluginsSpotubePluginMusicbrainzListenbrainzGen();
/// File path: assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug
String get plugin =>
'assets/plugins/spotube-plugin-musicbrainz-listenbrainz/plugin.smplug';
/// List of all assets
List<AssetGenImage> get values =>
[dabMusic, invidious, jiosaavn, songlinkTransparent];
List<String> get values => [plugin];
}
class $AssetsPluginsSpotubePluginYoutubeAudioGen {
const $AssetsPluginsSpotubePluginYoutubeAudioGen();
/// File path: assets/plugins/spotube-plugin-youtube-audio/plugin.smplug
String get plugin =>
'assets/plugins/spotube-plugin-youtube-audio/plugin.smplug';
/// List of all assets
List<String> get values => [plugin];
}
class Assets {
@ -96,6 +126,7 @@ class Assets {
static const String license = 'LICENSE';
static const $AssetsBrandingGen branding = $AssetsBrandingGen();
static const $AssetsImagesGen images = $AssetsImagesGen();
static const $AssetsPluginsGen plugins = $AssetsPluginsGen();
/// List of all assets
static List<String> get values => [license];

View File

@ -4,7 +4,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:spotube/collections/assets.gen.dart';
import 'package:spotube/collections/routes.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/components/ui/button_tile.dart';
@ -36,7 +35,6 @@ class TrackOptions extends HookConsumerWidget {
@override
Widget build(BuildContext context, ref) {
final mediaQuery = MediaQuery.of(context);
final ThemeData(:colorScheme) = Theme.of(context);
final trackOptionActions = ref.watch(trackOptionActionsProvider(track));
final (
@ -260,24 +258,6 @@ class TrackOptions extends HookConsumerWidget {
leading: const Icon(SpotubeIcons.share),
title: Text(context.l10n.share),
),
if (!isLocalTrack)
ButtonTile(
style: ButtonVariance.menu,
onPressed: () async {
await trackOptionActions.action(
rootNavigatorKey.currentContext!,
TrackOptionValue.songlink,
playlistId,
);
onTapItem?.call();
},
leading: Assets.images.logos.songlinkTransparent.image(
width: 22,
height: 22,
color: colorScheme.foreground.withValues(alpha: 0.5),
),
title: Text(context.l10n.song_link),
),
if (!isLocalTrack)
ButtonTile(
style: ButtonVariance.menu,

View File

@ -4143,8 +4143,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
late final $PluginsTableTable pluginsTable = $PluginsTableTable(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_info, source_type)');
@override
Iterable<TableInfo<Table, Object?>> get allTables =>
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
@ -4160,8 +4158,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
historyTable,
lyricsTable,
pluginsTable,
uniqueBlacklist,
uniqTrackMatch
uniqueBlacklist
];
}

View File

@ -10,6 +10,8 @@ enum SpotubeMediaCompressionType {
@Freezed(unionKey: 'type')
class SpotubeAudioSourceContainerPreset
with _$SpotubeAudioSourceContainerPreset {
const SpotubeAudioSourceContainerPreset._();
@FreezedUnionValue("lossy")
factory SpotubeAudioSourceContainerPreset.lossy({
required SpotubeMediaCompressionType type,
@ -27,6 +29,14 @@ class SpotubeAudioSourceContainerPreset
factory SpotubeAudioSourceContainerPreset.fromJson(
Map<String, dynamic> json) =>
_$SpotubeAudioSourceContainerPresetFromJson(json);
String getFileExtension() {
return switch (name) {
"mp4" => "m4a",
"webm" => "weba",
_ => name,
};
}
}
@freezed

View File

@ -197,12 +197,13 @@ class __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$SpotubeAudioSourceContainerPresetLossyImpl
implements SpotubeAudioSourceContainerPresetLossy {
extends SpotubeAudioSourceContainerPresetLossy {
_$SpotubeAudioSourceContainerPresetLossyImpl(
{required this.type,
required this.name,
required final List<SpotubeAudioLossyContainerQuality> qualities})
: _qualities = qualities;
: _qualities = qualities,
super._();
factory _$SpotubeAudioSourceContainerPresetLossyImpl.fromJson(
Map<String, dynamic> json) =>
@ -338,12 +339,13 @@ class _$SpotubeAudioSourceContainerPresetLossyImpl
}
abstract class SpotubeAudioSourceContainerPresetLossy
implements SpotubeAudioSourceContainerPreset {
extends SpotubeAudioSourceContainerPreset {
factory SpotubeAudioSourceContainerPresetLossy(
{required final SpotubeMediaCompressionType type,
required final String name,
required final List<SpotubeAudioLossyContainerQuality> qualities}) =
_$SpotubeAudioSourceContainerPresetLossyImpl;
SpotubeAudioSourceContainerPresetLossy._() : super._();
factory SpotubeAudioSourceContainerPresetLossy.fromJson(
Map<String, dynamic> json) =
@ -419,12 +421,13 @@ class __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl<$Res>
/// @nodoc
@JsonSerializable()
class _$SpotubeAudioSourceContainerPresetLosslessImpl
implements SpotubeAudioSourceContainerPresetLossless {
extends SpotubeAudioSourceContainerPresetLossless {
_$SpotubeAudioSourceContainerPresetLosslessImpl(
{required this.type,
required this.name,
required final List<SpotubeAudioLosslessContainerQuality> qualities})
: _qualities = qualities;
: _qualities = qualities,
super._();
factory _$SpotubeAudioSourceContainerPresetLosslessImpl.fromJson(
Map<String, dynamic> json) =>
@ -561,12 +564,13 @@ class _$SpotubeAudioSourceContainerPresetLosslessImpl
}
abstract class SpotubeAudioSourceContainerPresetLossless
implements SpotubeAudioSourceContainerPreset {
extends SpotubeAudioSourceContainerPreset {
factory SpotubeAudioSourceContainerPresetLossless(
{required final SpotubeMediaCompressionType type,
required final String name,
required final List<SpotubeAudioLosslessContainerQuality>
qualities}) = _$SpotubeAudioSourceContainerPresetLosslessImpl;
SpotubeAudioSourceContainerPresetLossless._() : super._();
factory SpotubeAudioSourceContainerPresetLossless.fromJson(
Map<String, dynamic> json) =

View File

@ -16,7 +16,7 @@ class UserDownloadsPage extends HookConsumerWidget {
Widget build(BuildContext context, ref) {
final downloadManager = ref.watch(downloadManagerProvider);
final history = downloadManager.$backHistory;
final history = downloadManager.$history;
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -48,7 +48,7 @@ class UserDownloadsPage extends HookConsumerWidget {
child: ListView.builder(
itemCount: history.length,
itemBuilder: (context, index) {
return DownloadItem(track: history.elementAt(index));
return DownloadItem(track: history.elementAt(index).query);
},
),
),

View File

@ -346,9 +346,11 @@ class LocalLibraryPage extends HookConsumerWidget {
controller: controller,
child: Skeletonizer(
enabled: trackSnapshot.isLoading,
child: ListView.builder(
child: CustomScrollView(
controller: controller,
physics: const AlwaysScrollableScrollPhysics(),
slivers: [
SliverList.builder(
itemCount: trackSnapshot.isLoading
? 5
: filteredTracks.length,
@ -377,6 +379,9 @@ class LocalLibraryPage extends HookConsumerWidget {
);
},
),
const SliverGap(200),
],
),
),
),
),
@ -398,7 +403,7 @@ class LocalLibraryPage extends HookConsumerWidget {
error: (error, stackTrace) =>
Text(error.toString() + stackTrace.toString()),
);
})
}),
],
),
),

View File

@ -19,7 +19,6 @@ import 'package:spotube/utils/service_utils.dart';
class DownloadManagerProvider extends ChangeNotifier {
DownloadManagerProvider({required this.ref})
: $history = <SourcedTrack>{},
$backHistory = <SpotubeFullTrackObject>{},
dl = DownloadManager() {
dl.statusStream.listen((event) async {
try {
@ -28,14 +27,13 @@ class DownloadManagerProvider extends ChangeNotifier {
final sourcedTrack = $history.firstWhereOrNull(
(element) =>
element.getUrlOfQuality(
downloadContainer, downloadQualityIndex) ==
downloadContainer,
downloadQualityIndex,
) ==
request.url,
);
if (sourcedTrack == null) return;
final track = $backHistory.firstWhereOrNull(
(element) => element.id == sourcedTrack.query.id,
);
if (track == null) return;
final savePath = getTrackFileUrl(sourcedTrack);
// related to onFileExists
@ -47,12 +45,12 @@ class DownloadManagerProvider extends ChangeNotifier {
await oldFile.exists()) {
await oldFile.rename(savePath);
}
if (status != DownloadStatus.completed ||
//? WebA audiotagging is not supported yet
//? Although in future by converting weba to opus & then tagging it
//? is possible using vorbis comments
downloadContainer.name == "weba" ||
downloadContainer.name == "webm") {
downloadContainer.getFileExtension() == "weba") {
return;
}
@ -63,13 +61,13 @@ class DownloadManagerProvider extends ChangeNotifier {
}
final imageBytes = await ServiceUtils.downloadImage(
(track.album.images).asUrlString(
(sourcedTrack.query.album.images).asUrlString(
placeholder: ImagePlaceholder.albumArt,
index: 1,
),
);
final metadata = track.toMetadata(
final metadata = sourcedTrack.query.toMetadata(
fileLength: await file.length(),
imageBytes: imageBytes,
);
@ -111,17 +109,16 @@ class DownloadManagerProvider extends ChangeNotifier {
final Set<SourcedTrack> $history;
// these are the tracks which metadata hasn't been fetched yet
final Set<SpotubeFullTrackObject> $backHistory;
final DownloadManager dl;
String getTrackFileUrl(SourcedTrack track) {
final name =
"${track.query.name} - ${track.query.artists.join(", ")}.${downloadContainer.name}";
"${track.query.name} - ${track.query.artists.map((e) => e.name).join(", ")}.${downloadContainer.getFileExtension()}";
return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name));
}
bool isActive(SpotubeFullTrackObject track) {
if ($backHistory.contains(track)) return true;
if ($history.any((e) => e.query.id == track.id)) return true;
final sourcedTrack = $history.firstWhereOrNull(
(element) => element.query.id == track.id,
@ -146,9 +143,7 @@ class DownloadManagerProvider extends ChangeNotifier {
/// For singular downloads
Future<void> addToQueue(SpotubeFullTrackObject track) async {
final sourcedTrack = await ref.read(
sourcedTrackProvider(track).future,
);
final sourcedTrack = await ref.read(sourcedTrackProvider(track).future);
final savePath = getTrackFileUrl(sourcedTrack);
@ -161,7 +156,6 @@ class DownloadManagerProvider extends ChangeNotifier {
await oldFile.rename("$savePath.old");
}
if (sourcedTrack.qualityPreset == downloadContainer) {
final downloadTask = await dl.addDownload(
sourcedTrack.getUrlOfQuality(downloadContainer, downloadQualityIndex)!,
savePath,
@ -169,27 +163,10 @@ class DownloadManagerProvider extends ChangeNotifier {
if (downloadTask != null) {
$history.add(sourcedTrack);
}
} else {
$backHistory.add(track);
final sourcedTrack =
await ref.read(sourcedTrackProvider(track).future).then((d) {
$backHistory.remove(track);
return d;
});
final downloadTask = await dl.addDownload(
sourcedTrack.getUrlOfQuality(downloadContainer, downloadQualityIndex)!,
savePath,
);
if (downloadTask != null) {
$history.add(sourcedTrack);
}
}
notifyListeners();
}
Future<void> batchAddToQueue(List<SpotubeFullTrackObject> tracks) async {
$backHistory.addAll(tracks);
notifyListeners();
for (final track in tracks) {
try {

View File

@ -48,7 +48,7 @@ class ServerPlaybackRoutes {
return join(
await UserPreferencesNotifier.getMusicCacheDir(),
ServiceUtils.sanitizeFilename(
'${track.query.name} - ${track.query.artists.map((d) => d.name).join(",")} (${track.info.id}).${track.qualityPreset!.name}',
'${track.query.name} - ${track.query.artists.map((d) => d.name).join(",")} (${track.info.id}).${track.qualityPreset!.getFileExtension()}',
),
);
}
@ -263,8 +263,7 @@ class ServerPlaybackRoutes {
}
if (contentRange.total == fileLength &&
track.qualityPreset!.name != "webm" ||
track.qualityPreset!.name != "weba") {
track.qualityPreset!.getFileExtension() != "weba") {
final playlistTrack = playlist.tracks.firstWhereOrNull(
(element) => element.id == track.query.id,
);

View File

@ -21,12 +21,10 @@ import 'package:spotube/provider/metadata_plugin/library/playlists.dart';
import 'package:spotube/provider/metadata_plugin/library/tracks.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:spotube/services/metadata/errors/exceptions.dart';
import 'package:url_launcher/url_launcher_string.dart';
enum TrackOptionValue {
album,
share,
songlink,
addToPlaylist,
addToQueue,
removeFromPlaylist,
@ -237,10 +235,6 @@ class TrackOptionsActions {
case TrackOptionValue.share:
actionShare(context);
break;
case TrackOptionValue.songlink:
final url = "https://song.link/s/${track.id}";
await launchUrlString(url);
break;
case TrackOptionValue.details:
if (track is! SpotubeFullTrackObject) break;
showDialog(
@ -252,8 +246,8 @@ class TrackOptionsActions {
);
break;
case TrackOptionValue.download:
if (track is! SpotubeFullTrackObject) break;
await downloadManager.addToQueue(track as SpotubeFullTrackObject);
if (track is SpotubeLocalTrackObject) break;
downloadManager.addToQueue(track as SpotubeFullTrackObject);
break;
case TrackOptionValue.startRadio:
actionStartRadio(context);

View File

@ -37,7 +37,7 @@ class SpotubeMedia extends mk.Media {
factory SpotubeMedia.media(Media media) {
assert(media.extras != null, "[Media] must have extra metadata set");
return SpotubeMedia(SpotubeFullTrackObject.fromJson(media.extras!));
return SpotubeMedia(SpotubeTrackObject.fromJson(media.extras!));
}
}

View File

@ -1,19 +0,0 @@
part of './song_link.dart';
@freezed
class SongLink with _$SongLink {
const factory SongLink({
required String displayName,
required String linkId,
required String platform,
required bool show,
required String? uniqueId,
required String? country,
required String? url,
required String? nativeAppUriMobile,
required String? nativeAppUriDesktop,
}) = _SongLink;
factory SongLink.fromJson(Map<String, dynamic> json) =>
_$SongLinkFromJson(json);
}

View File

@ -1,54 +0,0 @@
library song_link;
import 'dart:convert';
import 'package:spotube/services/logger/logger.dart';
import 'package:dio/dio.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:html/parser.dart';
part 'model.dart';
part 'song_link.freezed.dart';
part 'song_link.g.dart';
abstract class SongLinkService {
static final dio = Dio();
static Future<List<SongLink>> links(String spotifyId) async {
try {
final res = await dio.get(
"https://song.link/s/$spotifyId",
options: Options(
headers: {
"Accept":
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8",
"User-Agent":
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
},
responseType: ResponseType.plain,
),
);
final document = parse(res.data);
final script = document.getElementById("__NEXT_DATA__")?.text;
if (script == null) {
return <SongLink>[];
}
final pageProps = jsonDecode(script) as Map<String, dynamic>;
final songLinks = pageProps["props"]?["pageProps"]?["pageData"]
?["sections"]
?.firstWhere(
(section) => section?["sectionId"] == "section|auto|links|listen",
)?["links"] as List?;
return songLinks?.map((link) => SongLink.fromJson(link)).toList() ??
<SongLink>[];
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
return <SongLink>[];
}
}
}

View File

@ -1,333 +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 'song_link.dart';
// **************************************************************************
// FreezedGenerator
// **************************************************************************
T _$identity<T>(T value) => value;
final _privateConstructorUsedError = UnsupportedError(
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models');
SongLink _$SongLinkFromJson(Map<String, dynamic> json) {
return _SongLink.fromJson(json);
}
/// @nodoc
mixin _$SongLink {
String get displayName => throw _privateConstructorUsedError;
String get linkId => throw _privateConstructorUsedError;
String get platform => throw _privateConstructorUsedError;
bool get show => throw _privateConstructorUsedError;
String? get uniqueId => throw _privateConstructorUsedError;
String? get country => throw _privateConstructorUsedError;
String? get url => throw _privateConstructorUsedError;
String? get nativeAppUriMobile => throw _privateConstructorUsedError;
String? get nativeAppUriDesktop => throw _privateConstructorUsedError;
/// Serializes this SongLink to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$SongLinkCopyWith<SongLink> get copyWith =>
throw _privateConstructorUsedError;
}
/// @nodoc
abstract class $SongLinkCopyWith<$Res> {
factory $SongLinkCopyWith(SongLink value, $Res Function(SongLink) then) =
_$SongLinkCopyWithImpl<$Res, SongLink>;
@useResult
$Res call(
{String displayName,
String linkId,
String platform,
bool show,
String? uniqueId,
String? country,
String? url,
String? nativeAppUriMobile,
String? nativeAppUriDesktop});
}
/// @nodoc
class _$SongLinkCopyWithImpl<$Res, $Val extends SongLink>
implements $SongLinkCopyWith<$Res> {
_$SongLinkCopyWithImpl(this._value, this._then);
// ignore: unused_field
final $Val _value;
// ignore: unused_field
final $Res Function($Val) _then;
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? displayName = null,
Object? linkId = null,
Object? platform = null,
Object? show = null,
Object? uniqueId = freezed,
Object? country = freezed,
Object? url = freezed,
Object? nativeAppUriMobile = freezed,
Object? nativeAppUriDesktop = freezed,
}) {
return _then(_value.copyWith(
displayName: null == displayName
? _value.displayName
: displayName // ignore: cast_nullable_to_non_nullable
as String,
linkId: null == linkId
? _value.linkId
: linkId // ignore: cast_nullable_to_non_nullable
as String,
platform: null == platform
? _value.platform
: platform // ignore: cast_nullable_to_non_nullable
as String,
show: null == show
? _value.show
: show // ignore: cast_nullable_to_non_nullable
as bool,
uniqueId: freezed == uniqueId
? _value.uniqueId
: uniqueId // ignore: cast_nullable_to_non_nullable
as String?,
country: freezed == country
? _value.country
: country // ignore: cast_nullable_to_non_nullable
as String?,
url: freezed == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String?,
nativeAppUriMobile: freezed == nativeAppUriMobile
? _value.nativeAppUriMobile
: nativeAppUriMobile // ignore: cast_nullable_to_non_nullable
as String?,
nativeAppUriDesktop: freezed == nativeAppUriDesktop
? _value.nativeAppUriDesktop
: nativeAppUriDesktop // ignore: cast_nullable_to_non_nullable
as String?,
) as $Val);
}
}
/// @nodoc
abstract class _$$SongLinkImplCopyWith<$Res>
implements $SongLinkCopyWith<$Res> {
factory _$$SongLinkImplCopyWith(
_$SongLinkImpl value, $Res Function(_$SongLinkImpl) then) =
__$$SongLinkImplCopyWithImpl<$Res>;
@override
@useResult
$Res call(
{String displayName,
String linkId,
String platform,
bool show,
String? uniqueId,
String? country,
String? url,
String? nativeAppUriMobile,
String? nativeAppUriDesktop});
}
/// @nodoc
class __$$SongLinkImplCopyWithImpl<$Res>
extends _$SongLinkCopyWithImpl<$Res, _$SongLinkImpl>
implements _$$SongLinkImplCopyWith<$Res> {
__$$SongLinkImplCopyWithImpl(
_$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then)
: super(_value, _then);
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline')
@override
$Res call({
Object? displayName = null,
Object? linkId = null,
Object? platform = null,
Object? show = null,
Object? uniqueId = freezed,
Object? country = freezed,
Object? url = freezed,
Object? nativeAppUriMobile = freezed,
Object? nativeAppUriDesktop = freezed,
}) {
return _then(_$SongLinkImpl(
displayName: null == displayName
? _value.displayName
: displayName // ignore: cast_nullable_to_non_nullable
as String,
linkId: null == linkId
? _value.linkId
: linkId // ignore: cast_nullable_to_non_nullable
as String,
platform: null == platform
? _value.platform
: platform // ignore: cast_nullable_to_non_nullable
as String,
show: null == show
? _value.show
: show // ignore: cast_nullable_to_non_nullable
as bool,
uniqueId: freezed == uniqueId
? _value.uniqueId
: uniqueId // ignore: cast_nullable_to_non_nullable
as String?,
country: freezed == country
? _value.country
: country // ignore: cast_nullable_to_non_nullable
as String?,
url: freezed == url
? _value.url
: url // ignore: cast_nullable_to_non_nullable
as String?,
nativeAppUriMobile: freezed == nativeAppUriMobile
? _value.nativeAppUriMobile
: nativeAppUriMobile // ignore: cast_nullable_to_non_nullable
as String?,
nativeAppUriDesktop: freezed == nativeAppUriDesktop
? _value.nativeAppUriDesktop
: nativeAppUriDesktop // ignore: cast_nullable_to_non_nullable
as String?,
));
}
}
/// @nodoc
@JsonSerializable()
class _$SongLinkImpl implements _SongLink {
const _$SongLinkImpl(
{required this.displayName,
required this.linkId,
required this.platform,
required this.show,
required this.uniqueId,
required this.country,
required this.url,
required this.nativeAppUriMobile,
required this.nativeAppUriDesktop});
factory _$SongLinkImpl.fromJson(Map<String, dynamic> json) =>
_$$SongLinkImplFromJson(json);
@override
final String displayName;
@override
final String linkId;
@override
final String platform;
@override
final bool show;
@override
final String? uniqueId;
@override
final String? country;
@override
final String? url;
@override
final String? nativeAppUriMobile;
@override
final String? nativeAppUriDesktop;
@override
String toString() {
return 'SongLink(displayName: $displayName, linkId: $linkId, platform: $platform, show: $show, uniqueId: $uniqueId, country: $country, url: $url, nativeAppUriMobile: $nativeAppUriMobile, nativeAppUriDesktop: $nativeAppUriDesktop)';
}
@override
bool operator ==(Object other) {
return identical(this, other) ||
(other.runtimeType == runtimeType &&
other is _$SongLinkImpl &&
(identical(other.displayName, displayName) ||
other.displayName == displayName) &&
(identical(other.linkId, linkId) || other.linkId == linkId) &&
(identical(other.platform, platform) ||
other.platform == platform) &&
(identical(other.show, show) || other.show == show) &&
(identical(other.uniqueId, uniqueId) ||
other.uniqueId == uniqueId) &&
(identical(other.country, country) || other.country == country) &&
(identical(other.url, url) || other.url == url) &&
(identical(other.nativeAppUriMobile, nativeAppUriMobile) ||
other.nativeAppUriMobile == nativeAppUriMobile) &&
(identical(other.nativeAppUriDesktop, nativeAppUriDesktop) ||
other.nativeAppUriDesktop == nativeAppUriDesktop));
}
@JsonKey(includeFromJson: false, includeToJson: false)
@override
int get hashCode => Object.hash(runtimeType, displayName, linkId, platform,
show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop);
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override
@pragma('vm:prefer-inline')
_$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
__$$SongLinkImplCopyWithImpl<_$SongLinkImpl>(this, _$identity);
@override
Map<String, dynamic> toJson() {
return _$$SongLinkImplToJson(
this,
);
}
}
abstract class _SongLink implements SongLink {
const factory _SongLink(
{required final String displayName,
required final String linkId,
required final String platform,
required final bool show,
required final String? uniqueId,
required final String? country,
required final String? url,
required final String? nativeAppUriMobile,
required final String? nativeAppUriDesktop}) = _$SongLinkImpl;
factory _SongLink.fromJson(Map<String, dynamic> json) =
_$SongLinkImpl.fromJson;
@override
String get displayName;
@override
String get linkId;
@override
String get platform;
@override
bool get show;
@override
String? get uniqueId;
@override
String? get country;
@override
String? get url;
@override
String? get nativeAppUriMobile;
@override
String? get nativeAppUriDesktop;
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@override
@JsonKey(includeFromJson: false, includeToJson: false)
_$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
throw _privateConstructorUsedError;
}

View File

@ -1,32 +0,0 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'song_link.dart';
// **************************************************************************
// JsonSerializableGenerator
// **************************************************************************
_$SongLinkImpl _$$SongLinkImplFromJson(Map json) => _$SongLinkImpl(
displayName: json['displayName'] as String,
linkId: json['linkId'] as String,
platform: json['platform'] as String,
show: json['show'] as bool,
uniqueId: json['uniqueId'] as String?,
country: json['country'] as String?,
url: json['url'] as String?,
nativeAppUriMobile: json['nativeAppUriMobile'] as String?,
nativeAppUriDesktop: json['nativeAppUriDesktop'] as String?,
);
Map<String, dynamic> _$$SongLinkImplToJson(_$SongLinkImpl instance) =>
<String, dynamic>{
'displayName': instance.displayName,
'linkId': instance.linkId,
'platform': instance.platform,
'show': instance.show,
'uniqueId': instance.uniqueId,
'country': instance.country,
'url': instance.url,
'nativeAppUriMobile': instance.nativeAppUriMobile,
'nativeAppUriDesktop': instance.nativeAppUriDesktop,
};