mirror of
https://github.com/KRTirtho/spotube.git
synced 2025-12-06 07:29:42 +00:00
fix: downloaded tracks are not tagged with metadata
This commit is contained in:
parent
700a69fcd1
commit
3209c75144
@ -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. [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. [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. [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. [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. [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
|
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 |
@ -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 {
|
class $AssetsImagesLogosGen {
|
||||||
const $AssetsImagesLogosGen();
|
const $AssetsImagesLogosGen();
|
||||||
|
|
||||||
@ -81,13 +94,30 @@ class $AssetsImagesLogosGen {
|
|||||||
AssetGenImage get jiosaavn =>
|
AssetGenImage get jiosaavn =>
|
||||||
const AssetGenImage('assets/images/logos/jiosaavn.png');
|
const AssetGenImage('assets/images/logos/jiosaavn.png');
|
||||||
|
|
||||||
/// File path: assets/images/logos/songlink-transparent.png
|
/// List of all assets
|
||||||
AssetGenImage get songlinkTransparent =>
|
List<AssetGenImage> get values => [dabMusic, invidious, jiosaavn];
|
||||||
const AssetGenImage('assets/images/logos/songlink-transparent.png');
|
}
|
||||||
|
|
||||||
|
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 of all assets
|
||||||
List<AssetGenImage> get values =>
|
List<String> get values => [plugin];
|
||||||
[dabMusic, invidious, jiosaavn, songlinkTransparent];
|
}
|
||||||
|
|
||||||
|
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 {
|
class Assets {
|
||||||
@ -96,6 +126,7 @@ class Assets {
|
|||||||
static const String license = 'LICENSE';
|
static const String license = 'LICENSE';
|
||||||
static const $AssetsBrandingGen branding = $AssetsBrandingGen();
|
static const $AssetsBrandingGen branding = $AssetsBrandingGen();
|
||||||
static const $AssetsImagesGen images = $AssetsImagesGen();
|
static const $AssetsImagesGen images = $AssetsImagesGen();
|
||||||
|
static const $AssetsPluginsGen plugins = $AssetsPluginsGen();
|
||||||
|
|
||||||
/// List of all assets
|
/// List of all assets
|
||||||
static List<String> get values => [license];
|
static List<String> get values => [license];
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart';
|
|||||||
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
import 'package:shadcn_flutter/shadcn_flutter.dart';
|
||||||
|
|
||||||
import 'package:shadcn_flutter/shadcn_flutter_extension.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/routes.dart';
|
||||||
import 'package:spotube/collections/spotube_icons.dart';
|
import 'package:spotube/collections/spotube_icons.dart';
|
||||||
import 'package:spotube/components/ui/button_tile.dart';
|
import 'package:spotube/components/ui/button_tile.dart';
|
||||||
@ -36,7 +35,6 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final mediaQuery = MediaQuery.of(context);
|
final mediaQuery = MediaQuery.of(context);
|
||||||
final ThemeData(:colorScheme) = Theme.of(context);
|
|
||||||
|
|
||||||
final trackOptionActions = ref.watch(trackOptionActionsProvider(track));
|
final trackOptionActions = ref.watch(trackOptionActionsProvider(track));
|
||||||
final (
|
final (
|
||||||
@ -260,24 +258,6 @@ class TrackOptions extends HookConsumerWidget {
|
|||||||
leading: const Icon(SpotubeIcons.share),
|
leading: const Icon(SpotubeIcons.share),
|
||||||
title: Text(context.l10n.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)
|
if (!isLocalTrack)
|
||||||
ButtonTile(
|
ButtonTile(
|
||||||
style: ButtonVariance.menu,
|
style: ButtonVariance.menu,
|
||||||
|
|||||||
@ -4143,8 +4143,6 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
late final $PluginsTableTable pluginsTable = $PluginsTableTable(this);
|
late final $PluginsTableTable pluginsTable = $PluginsTableTable(this);
|
||||||
late final Index uniqueBlacklist = Index('unique_blacklist',
|
late final Index uniqueBlacklist = Index('unique_blacklist',
|
||||||
'CREATE UNIQUE INDEX unique_blacklist ON blacklist_table (element_type, element_id)');
|
'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
|
@override
|
||||||
Iterable<TableInfo<Table, Object?>> get allTables =>
|
Iterable<TableInfo<Table, Object?>> get allTables =>
|
||||||
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
allSchemaEntities.whereType<TableInfo<Table, Object?>>();
|
||||||
@ -4160,8 +4158,7 @@ abstract class _$AppDatabase extends GeneratedDatabase {
|
|||||||
historyTable,
|
historyTable,
|
||||||
lyricsTable,
|
lyricsTable,
|
||||||
pluginsTable,
|
pluginsTable,
|
||||||
uniqueBlacklist,
|
uniqueBlacklist
|
||||||
uniqTrackMatch
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -10,6 +10,8 @@ enum SpotubeMediaCompressionType {
|
|||||||
@Freezed(unionKey: 'type')
|
@Freezed(unionKey: 'type')
|
||||||
class SpotubeAudioSourceContainerPreset
|
class SpotubeAudioSourceContainerPreset
|
||||||
with _$SpotubeAudioSourceContainerPreset {
|
with _$SpotubeAudioSourceContainerPreset {
|
||||||
|
const SpotubeAudioSourceContainerPreset._();
|
||||||
|
|
||||||
@FreezedUnionValue("lossy")
|
@FreezedUnionValue("lossy")
|
||||||
factory SpotubeAudioSourceContainerPreset.lossy({
|
factory SpotubeAudioSourceContainerPreset.lossy({
|
||||||
required SpotubeMediaCompressionType type,
|
required SpotubeMediaCompressionType type,
|
||||||
@ -27,6 +29,14 @@ class SpotubeAudioSourceContainerPreset
|
|||||||
factory SpotubeAudioSourceContainerPreset.fromJson(
|
factory SpotubeAudioSourceContainerPreset.fromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
_$SpotubeAudioSourceContainerPresetFromJson(json);
|
_$SpotubeAudioSourceContainerPresetFromJson(json);
|
||||||
|
|
||||||
|
String getFileExtension() {
|
||||||
|
return switch (name) {
|
||||||
|
"mp4" => "m4a",
|
||||||
|
"webm" => "weba",
|
||||||
|
_ => name,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@freezed
|
@freezed
|
||||||
|
|||||||
@ -197,12 +197,13 @@ class __$$SpotubeAudioSourceContainerPresetLossyImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$SpotubeAudioSourceContainerPresetLossyImpl
|
class _$SpotubeAudioSourceContainerPresetLossyImpl
|
||||||
implements SpotubeAudioSourceContainerPresetLossy {
|
extends SpotubeAudioSourceContainerPresetLossy {
|
||||||
_$SpotubeAudioSourceContainerPresetLossyImpl(
|
_$SpotubeAudioSourceContainerPresetLossyImpl(
|
||||||
{required this.type,
|
{required this.type,
|
||||||
required this.name,
|
required this.name,
|
||||||
required final List<SpotubeAudioLossyContainerQuality> qualities})
|
required final List<SpotubeAudioLossyContainerQuality> qualities})
|
||||||
: _qualities = qualities;
|
: _qualities = qualities,
|
||||||
|
super._();
|
||||||
|
|
||||||
factory _$SpotubeAudioSourceContainerPresetLossyImpl.fromJson(
|
factory _$SpotubeAudioSourceContainerPresetLossyImpl.fromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
@ -338,12 +339,13 @@ class _$SpotubeAudioSourceContainerPresetLossyImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class SpotubeAudioSourceContainerPresetLossy
|
abstract class SpotubeAudioSourceContainerPresetLossy
|
||||||
implements SpotubeAudioSourceContainerPreset {
|
extends SpotubeAudioSourceContainerPreset {
|
||||||
factory SpotubeAudioSourceContainerPresetLossy(
|
factory SpotubeAudioSourceContainerPresetLossy(
|
||||||
{required final SpotubeMediaCompressionType type,
|
{required final SpotubeMediaCompressionType type,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final List<SpotubeAudioLossyContainerQuality> qualities}) =
|
required final List<SpotubeAudioLossyContainerQuality> qualities}) =
|
||||||
_$SpotubeAudioSourceContainerPresetLossyImpl;
|
_$SpotubeAudioSourceContainerPresetLossyImpl;
|
||||||
|
SpotubeAudioSourceContainerPresetLossy._() : super._();
|
||||||
|
|
||||||
factory SpotubeAudioSourceContainerPresetLossy.fromJson(
|
factory SpotubeAudioSourceContainerPresetLossy.fromJson(
|
||||||
Map<String, dynamic> json) =
|
Map<String, dynamic> json) =
|
||||||
@ -419,12 +421,13 @@ class __$$SpotubeAudioSourceContainerPresetLosslessImplCopyWithImpl<$Res>
|
|||||||
/// @nodoc
|
/// @nodoc
|
||||||
@JsonSerializable()
|
@JsonSerializable()
|
||||||
class _$SpotubeAudioSourceContainerPresetLosslessImpl
|
class _$SpotubeAudioSourceContainerPresetLosslessImpl
|
||||||
implements SpotubeAudioSourceContainerPresetLossless {
|
extends SpotubeAudioSourceContainerPresetLossless {
|
||||||
_$SpotubeAudioSourceContainerPresetLosslessImpl(
|
_$SpotubeAudioSourceContainerPresetLosslessImpl(
|
||||||
{required this.type,
|
{required this.type,
|
||||||
required this.name,
|
required this.name,
|
||||||
required final List<SpotubeAudioLosslessContainerQuality> qualities})
|
required final List<SpotubeAudioLosslessContainerQuality> qualities})
|
||||||
: _qualities = qualities;
|
: _qualities = qualities,
|
||||||
|
super._();
|
||||||
|
|
||||||
factory _$SpotubeAudioSourceContainerPresetLosslessImpl.fromJson(
|
factory _$SpotubeAudioSourceContainerPresetLosslessImpl.fromJson(
|
||||||
Map<String, dynamic> json) =>
|
Map<String, dynamic> json) =>
|
||||||
@ -561,12 +564,13 @@ class _$SpotubeAudioSourceContainerPresetLosslessImpl
|
|||||||
}
|
}
|
||||||
|
|
||||||
abstract class SpotubeAudioSourceContainerPresetLossless
|
abstract class SpotubeAudioSourceContainerPresetLossless
|
||||||
implements SpotubeAudioSourceContainerPreset {
|
extends SpotubeAudioSourceContainerPreset {
|
||||||
factory SpotubeAudioSourceContainerPresetLossless(
|
factory SpotubeAudioSourceContainerPresetLossless(
|
||||||
{required final SpotubeMediaCompressionType type,
|
{required final SpotubeMediaCompressionType type,
|
||||||
required final String name,
|
required final String name,
|
||||||
required final List<SpotubeAudioLosslessContainerQuality>
|
required final List<SpotubeAudioLosslessContainerQuality>
|
||||||
qualities}) = _$SpotubeAudioSourceContainerPresetLosslessImpl;
|
qualities}) = _$SpotubeAudioSourceContainerPresetLosslessImpl;
|
||||||
|
SpotubeAudioSourceContainerPresetLossless._() : super._();
|
||||||
|
|
||||||
factory SpotubeAudioSourceContainerPresetLossless.fromJson(
|
factory SpotubeAudioSourceContainerPresetLossless.fromJson(
|
||||||
Map<String, dynamic> json) =
|
Map<String, dynamic> json) =
|
||||||
|
|||||||
@ -16,7 +16,7 @@ class UserDownloadsPage extends HookConsumerWidget {
|
|||||||
Widget build(BuildContext context, ref) {
|
Widget build(BuildContext context, ref) {
|
||||||
final downloadManager = ref.watch(downloadManagerProvider);
|
final downloadManager = ref.watch(downloadManagerProvider);
|
||||||
|
|
||||||
final history = downloadManager.$backHistory;
|
final history = downloadManager.$history;
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@ -48,7 +48,7 @@ class UserDownloadsPage extends HookConsumerWidget {
|
|||||||
child: ListView.builder(
|
child: ListView.builder(
|
||||||
itemCount: history.length,
|
itemCount: history.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
return DownloadItem(track: history.elementAt(index));
|
return DownloadItem(track: history.elementAt(index).query);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -346,9 +346,11 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
controller: controller,
|
controller: controller,
|
||||||
child: Skeletonizer(
|
child: Skeletonizer(
|
||||||
enabled: trackSnapshot.isLoading,
|
enabled: trackSnapshot.isLoading,
|
||||||
child: ListView.builder(
|
child: CustomScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
physics: const AlwaysScrollableScrollPhysics(),
|
physics: const AlwaysScrollableScrollPhysics(),
|
||||||
|
slivers: [
|
||||||
|
SliverList.builder(
|
||||||
itemCount: trackSnapshot.isLoading
|
itemCount: trackSnapshot.isLoading
|
||||||
? 5
|
? 5
|
||||||
: filteredTracks.length,
|
: filteredTracks.length,
|
||||||
@ -377,6 +379,9 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
const SliverGap(200),
|
||||||
|
],
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -398,7 +403,7 @@ class LocalLibraryPage extends HookConsumerWidget {
|
|||||||
error: (error, stackTrace) =>
|
error: (error, stackTrace) =>
|
||||||
Text(error.toString() + stackTrace.toString()),
|
Text(error.toString() + stackTrace.toString()),
|
||||||
);
|
);
|
||||||
})
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@ -19,7 +19,6 @@ import 'package:spotube/utils/service_utils.dart';
|
|||||||
class DownloadManagerProvider extends ChangeNotifier {
|
class DownloadManagerProvider extends ChangeNotifier {
|
||||||
DownloadManagerProvider({required this.ref})
|
DownloadManagerProvider({required this.ref})
|
||||||
: $history = <SourcedTrack>{},
|
: $history = <SourcedTrack>{},
|
||||||
$backHistory = <SpotubeFullTrackObject>{},
|
|
||||||
dl = DownloadManager() {
|
dl = DownloadManager() {
|
||||||
dl.statusStream.listen((event) async {
|
dl.statusStream.listen((event) async {
|
||||||
try {
|
try {
|
||||||
@ -28,14 +27,13 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
final sourcedTrack = $history.firstWhereOrNull(
|
final sourcedTrack = $history.firstWhereOrNull(
|
||||||
(element) =>
|
(element) =>
|
||||||
element.getUrlOfQuality(
|
element.getUrlOfQuality(
|
||||||
downloadContainer, downloadQualityIndex) ==
|
downloadContainer,
|
||||||
|
downloadQualityIndex,
|
||||||
|
) ==
|
||||||
request.url,
|
request.url,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (sourcedTrack == null) return;
|
if (sourcedTrack == null) return;
|
||||||
final track = $backHistory.firstWhereOrNull(
|
|
||||||
(element) => element.id == sourcedTrack.query.id,
|
|
||||||
);
|
|
||||||
if (track == null) return;
|
|
||||||
|
|
||||||
final savePath = getTrackFileUrl(sourcedTrack);
|
final savePath = getTrackFileUrl(sourcedTrack);
|
||||||
// related to onFileExists
|
// related to onFileExists
|
||||||
@ -47,12 +45,12 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
await oldFile.exists()) {
|
await oldFile.exists()) {
|
||||||
await oldFile.rename(savePath);
|
await oldFile.rename(savePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (status != DownloadStatus.completed ||
|
if (status != DownloadStatus.completed ||
|
||||||
//? WebA audiotagging is not supported yet
|
//? WebA audiotagging is not supported yet
|
||||||
//? Although in future by converting weba to opus & then tagging it
|
//? Although in future by converting weba to opus & then tagging it
|
||||||
//? is possible using vorbis comments
|
//? is possible using vorbis comments
|
||||||
downloadContainer.name == "weba" ||
|
downloadContainer.getFileExtension() == "weba") {
|
||||||
downloadContainer.name == "webm") {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,13 +61,13 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final imageBytes = await ServiceUtils.downloadImage(
|
final imageBytes = await ServiceUtils.downloadImage(
|
||||||
(track.album.images).asUrlString(
|
(sourcedTrack.query.album.images).asUrlString(
|
||||||
placeholder: ImagePlaceholder.albumArt,
|
placeholder: ImagePlaceholder.albumArt,
|
||||||
index: 1,
|
index: 1,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final metadata = track.toMetadata(
|
final metadata = sourcedTrack.query.toMetadata(
|
||||||
fileLength: await file.length(),
|
fileLength: await file.length(),
|
||||||
imageBytes: imageBytes,
|
imageBytes: imageBytes,
|
||||||
);
|
);
|
||||||
@ -111,17 +109,16 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
final Set<SourcedTrack> $history;
|
final Set<SourcedTrack> $history;
|
||||||
// these are the tracks which metadata hasn't been fetched yet
|
// these are the tracks which metadata hasn't been fetched yet
|
||||||
final Set<SpotubeFullTrackObject> $backHistory;
|
|
||||||
final DownloadManager dl;
|
final DownloadManager dl;
|
||||||
|
|
||||||
String getTrackFileUrl(SourcedTrack track) {
|
String getTrackFileUrl(SourcedTrack track) {
|
||||||
final name =
|
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));
|
return join(downloadDirectory, PrimitiveUtils.toSafeFileName(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool isActive(SpotubeFullTrackObject track) {
|
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(
|
final sourcedTrack = $history.firstWhereOrNull(
|
||||||
(element) => element.query.id == track.id,
|
(element) => element.query.id == track.id,
|
||||||
@ -146,9 +143,7 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
|
|
||||||
/// For singular downloads
|
/// For singular downloads
|
||||||
Future<void> addToQueue(SpotubeFullTrackObject track) async {
|
Future<void> addToQueue(SpotubeFullTrackObject track) async {
|
||||||
final sourcedTrack = await ref.read(
|
final sourcedTrack = await ref.read(sourcedTrackProvider(track).future);
|
||||||
sourcedTrackProvider(track).future,
|
|
||||||
);
|
|
||||||
|
|
||||||
final savePath = getTrackFileUrl(sourcedTrack);
|
final savePath = getTrackFileUrl(sourcedTrack);
|
||||||
|
|
||||||
@ -161,7 +156,6 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
await oldFile.rename("$savePath.old");
|
await oldFile.rename("$savePath.old");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourcedTrack.qualityPreset == downloadContainer) {
|
|
||||||
final downloadTask = await dl.addDownload(
|
final downloadTask = await dl.addDownload(
|
||||||
sourcedTrack.getUrlOfQuality(downloadContainer, downloadQualityIndex)!,
|
sourcedTrack.getUrlOfQuality(downloadContainer, downloadQualityIndex)!,
|
||||||
savePath,
|
savePath,
|
||||||
@ -169,27 +163,10 @@ class DownloadManagerProvider extends ChangeNotifier {
|
|||||||
if (downloadTask != null) {
|
if (downloadTask != null) {
|
||||||
$history.add(sourcedTrack);
|
$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();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> batchAddToQueue(List<SpotubeFullTrackObject> tracks) async {
|
Future<void> batchAddToQueue(List<SpotubeFullTrackObject> tracks) async {
|
||||||
$backHistory.addAll(tracks);
|
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
for (final track in tracks) {
|
for (final track in tracks) {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -48,7 +48,7 @@ class ServerPlaybackRoutes {
|
|||||||
return join(
|
return join(
|
||||||
await UserPreferencesNotifier.getMusicCacheDir(),
|
await UserPreferencesNotifier.getMusicCacheDir(),
|
||||||
ServiceUtils.sanitizeFilename(
|
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 &&
|
if (contentRange.total == fileLength &&
|
||||||
track.qualityPreset!.name != "webm" ||
|
track.qualityPreset!.getFileExtension() != "weba") {
|
||||||
track.qualityPreset!.name != "weba") {
|
|
||||||
final playlistTrack = playlist.tracks.firstWhereOrNull(
|
final playlistTrack = playlist.tracks.firstWhereOrNull(
|
||||||
(element) => element.id == track.query.id,
|
(element) => element.id == track.query.id,
|
||||||
);
|
);
|
||||||
|
|||||||
@ -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/library/tracks.dart';
|
||||||
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
|
||||||
import 'package:spotube/services/metadata/errors/exceptions.dart';
|
import 'package:spotube/services/metadata/errors/exceptions.dart';
|
||||||
import 'package:url_launcher/url_launcher_string.dart';
|
|
||||||
|
|
||||||
enum TrackOptionValue {
|
enum TrackOptionValue {
|
||||||
album,
|
album,
|
||||||
share,
|
share,
|
||||||
songlink,
|
|
||||||
addToPlaylist,
|
addToPlaylist,
|
||||||
addToQueue,
|
addToQueue,
|
||||||
removeFromPlaylist,
|
removeFromPlaylist,
|
||||||
@ -237,10 +235,6 @@ class TrackOptionsActions {
|
|||||||
case TrackOptionValue.share:
|
case TrackOptionValue.share:
|
||||||
actionShare(context);
|
actionShare(context);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.songlink:
|
|
||||||
final url = "https://song.link/s/${track.id}";
|
|
||||||
await launchUrlString(url);
|
|
||||||
break;
|
|
||||||
case TrackOptionValue.details:
|
case TrackOptionValue.details:
|
||||||
if (track is! SpotubeFullTrackObject) break;
|
if (track is! SpotubeFullTrackObject) break;
|
||||||
showDialog(
|
showDialog(
|
||||||
@ -252,8 +246,8 @@ class TrackOptionsActions {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.download:
|
case TrackOptionValue.download:
|
||||||
if (track is! SpotubeFullTrackObject) break;
|
if (track is SpotubeLocalTrackObject) break;
|
||||||
await downloadManager.addToQueue(track as SpotubeFullTrackObject);
|
downloadManager.addToQueue(track as SpotubeFullTrackObject);
|
||||||
break;
|
break;
|
||||||
case TrackOptionValue.startRadio:
|
case TrackOptionValue.startRadio:
|
||||||
actionStartRadio(context);
|
actionStartRadio(context);
|
||||||
|
|||||||
@ -37,7 +37,7 @@ class SpotubeMedia extends mk.Media {
|
|||||||
|
|
||||||
factory SpotubeMedia.media(Media media) {
|
factory SpotubeMedia.media(Media media) {
|
||||||
assert(media.extras != null, "[Media] must have extra metadata set");
|
assert(media.extras != null, "[Media] must have extra metadata set");
|
||||||
return SpotubeMedia(SpotubeFullTrackObject.fromJson(media.extras!));
|
return SpotubeMedia(SpotubeTrackObject.fromJson(media.extras!));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -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);
|
|
||||||
}
|
|
||||||
@ -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>[];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -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;
|
|
||||||
}
|
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
Loading…
Reference in New Issue
Block a user