Compare commits

..

1 Commits

Author SHA1 Message Date
Tobse
84cca37ace
Merge 24f186e9fd into a8f70f201e 2025-11-06 12:16:46 +01:00
90 changed files with 1237 additions and 2142 deletions

View File

@ -2,7 +2,6 @@ plugins {
id "com.android.application" id "com.android.application"
id "kotlin-android" id "kotlin-android"
id "dev.flutter.flutter-gradle-plugin" id "dev.flutter.flutter-gradle-plugin"
id "org.jetbrains.kotlin.plugin.compose"
} }
def localProperties = new Properties() def localProperties = new Properties()

View File

@ -19,8 +19,7 @@ pluginManagement {
plugins { plugins {
id "dev.flutter.flutter-plugin-loader" version "1.0.0" id "dev.flutter.flutter-plugin-loader" version "1.0.0"
id "com.android.application" version '8.7.0' apply false id "com.android.application" version '8.7.0' apply false
id "org.jetbrains.kotlin.android" version "2.1.0" apply false id "org.jetbrains.kotlin.android" version "1.8.22" apply false
id "org.jetbrains.kotlin.plugin.compose" version "2.1.0" apply false
} }
include ':app' include ':app'

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -39,11 +39,6 @@ class InstallDependenciesCommand extends Command {
switch (argResults!.option("platform")) { switch (argResults!.option("platform")) {
case "windows": case "windows":
await shell.run(
"""
choco install innosetup -y
""",
);
break; break;
case "linux": case "linux":
await shell.run( await shell.run(

View File

@ -1,5 +1,3 @@
// dart format width=80
/// GENERATED CODE - DO NOT MODIFY BY HAND /// GENERATED CODE - DO NOT MODIFY BY HAND
/// ***************************************************** /// *****************************************************
/// FlutterGen /// FlutterGen
@ -7,7 +5,7 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import // ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
@ -69,10 +67,6 @@ class $AssetsImagesGen {
class $AssetsImagesLogosGen { class $AssetsImagesLogosGen {
const $AssetsImagesLogosGen(); const $AssetsImagesLogosGen();
/// File path: assets/images/logos/dab-music.png
AssetGenImage get dabMusic =>
const AssetGenImage('assets/images/logos/dab-music.png');
/// File path: assets/images/logos/invidious.jpg /// File path: assets/images/logos/invidious.jpg
AssetGenImage get invidious => AssetGenImage get invidious =>
const AssetGenImage('assets/images/logos/invidious.jpg'); const AssetGenImage('assets/images/logos/invidious.jpg');
@ -86,12 +80,11 @@ class $AssetsImagesLogosGen {
const AssetGenImage('assets/images/logos/songlink-transparent.png'); const AssetGenImage('assets/images/logos/songlink-transparent.png');
/// List of all assets /// List of all assets
List<AssetGenImage> get values => List<AssetGenImage> get values => [invidious, jiosaavn, songlinkTransparent];
[dabMusic, invidious, jiosaavn, songlinkTransparent];
} }
class Assets { class Assets {
const Assets._(); Assets._();
static const String license = 'LICENSE'; static const String license = 'LICENSE';
static const $AssetsBrandingGen branding = $AssetsBrandingGen(); static const $AssetsBrandingGen branding = $AssetsBrandingGen();
@ -106,14 +99,12 @@ class AssetGenImage {
this._assetName, { this._assetName, {
this.size, this.size,
this.flavors = const {}, this.flavors = const {},
this.animation,
}); });
final String _assetName; final String _assetName;
final Size? size; final Size? size;
final Set<String> flavors; final Set<String> flavors;
final AssetGenImageAnimation? animation;
Image image({ Image image({
Key? key, Key? key,
@ -136,7 +127,7 @@ class AssetGenImage {
bool gaplessPlayback = true, bool gaplessPlayback = true,
bool isAntiAlias = false, bool isAntiAlias = false,
String? package, String? package,
FilterQuality filterQuality = FilterQuality.medium, FilterQuality filterQuality = FilterQuality.low,
int? cacheWidth, int? cacheWidth,
int? cacheHeight, int? cacheHeight,
}) { }) {
@ -183,15 +174,3 @@ class AssetGenImage {
String get keyName => _assetName; String get keyName => _assetName;
} }
class AssetGenImageAnimation {
const AssetGenImageAnimation({
required this.isAnimation,
required this.duration,
required this.frames,
});
final bool isAnimation;
final Duration duration;
final int frames;
}

View File

@ -1,4 +1,3 @@
// dart format width=80
/// GENERATED CODE - DO NOT MODIFY BY HAND /// GENERATED CODE - DO NOT MODIFY BY HAND
/// ***************************************************** /// *****************************************************
/// FlutterGen /// FlutterGen
@ -6,7 +5,7 @@
// coverage:ignore-file // coverage:ignore-file
// ignore_for_file: type=lint // ignore_for_file: type=lint
// ignore_for_file: deprecated_member_use,directives_ordering,implicit_dynamic_list_literal,unnecessary_import // ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
class FontFamily { class FontFamily {
FontFamily._(); FontFamily._();

View File

@ -1,4 +1,3 @@
// dart format width=80
// GENERATED CODE - DO NOT MODIFY BY HAND // GENERATED CODE - DO NOT MODIFY BY HAND
// ************************************************************************** // **************************************************************************
@ -60,7 +59,10 @@ import 'package:spotube/pages/track/track.dart' as _i35;
/// [_i1.AboutSpotubePage] /// [_i1.AboutSpotubePage]
class AboutSpotubeRoute extends _i41.PageRouteInfo<void> { class AboutSpotubeRoute extends _i41.PageRouteInfo<void> {
const AboutSpotubeRoute({List<_i41.PageRouteInfo>? children}) const AboutSpotubeRoute({List<_i41.PageRouteInfo>? children})
: super(AboutSpotubeRoute.name, initialChildren: children); : super(
AboutSpotubeRoute.name,
initialChildren: children,
);
static const String name = 'AboutSpotubeRoute'; static const String name = 'AboutSpotubeRoute';
@ -81,11 +83,15 @@ class AlbumRoute extends _i41.PageRouteInfo<AlbumRouteArgs> {
required _i43.SpotubeSimpleAlbumObject album, required _i43.SpotubeSimpleAlbumObject album,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
AlbumRoute.name, AlbumRoute.name,
args: AlbumRouteArgs(key: key, id: id, album: album), args: AlbumRouteArgs(
rawPathParams: {'id': id}, key: key,
initialChildren: children, id: id,
); album: album,
),
rawPathParams: {'id': id},
initialChildren: children,
);
static const String name = 'AlbumRoute'; static const String name = 'AlbumRoute';
@ -93,13 +99,21 @@ class AlbumRoute extends _i41.PageRouteInfo<AlbumRouteArgs> {
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<AlbumRouteArgs>(); final args = data.argsAs<AlbumRouteArgs>();
return _i2.AlbumPage(key: args.key, id: args.id, album: args.album); return _i2.AlbumPage(
key: args.key,
id: args.id,
album: args.album,
);
}, },
); );
} }
class AlbumRouteArgs { class AlbumRouteArgs {
const AlbumRouteArgs({this.key, required this.id, required this.album}); const AlbumRouteArgs({
this.key,
required this.id,
required this.album,
});
final _i42.Key? key; final _i42.Key? key;
@ -121,11 +135,14 @@ class ArtistRoute extends _i41.PageRouteInfo<ArtistRouteArgs> {
_i42.Key? key, _i42.Key? key,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
ArtistRoute.name, ArtistRoute.name,
args: ArtistRouteArgs(artistId: artistId, key: key), args: ArtistRouteArgs(
rawPathParams: {'id': artistId}, artistId: artistId,
initialChildren: children, key: key,
); ),
rawPathParams: {'id': artistId},
initialChildren: children,
);
static const String name = 'ArtistRoute'; static const String name = 'ArtistRoute';
@ -134,15 +151,20 @@ class ArtistRoute extends _i41.PageRouteInfo<ArtistRouteArgs> {
builder: (data) { builder: (data) {
final pathParams = data.inheritedPathParams; final pathParams = data.inheritedPathParams;
final args = data.argsAs<ArtistRouteArgs>( final args = data.argsAs<ArtistRouteArgs>(
orElse: () => ArtistRouteArgs(artistId: pathParams.getString('id')), orElse: () => ArtistRouteArgs(artistId: pathParams.getString('id')));
return _i3.ArtistPage(
args.artistId,
key: args.key,
); );
return _i3.ArtistPage(args.artistId, key: args.key);
}, },
); );
} }
class ArtistRouteArgs { class ArtistRouteArgs {
const ArtistRouteArgs({required this.artistId, this.key}); const ArtistRouteArgs({
required this.artistId,
this.key,
});
final String artistId; final String artistId;
@ -158,7 +180,10 @@ class ArtistRouteArgs {
/// [_i4.BlackListPage] /// [_i4.BlackListPage]
class BlackListRoute extends _i41.PageRouteInfo<void> { class BlackListRoute extends _i41.PageRouteInfo<void> {
const BlackListRoute({List<_i41.PageRouteInfo>? children}) const BlackListRoute({List<_i41.PageRouteInfo>? children})
: super(BlackListRoute.name, initialChildren: children); : super(
BlackListRoute.name,
initialChildren: children,
);
static const String name = 'BlackListRoute'; static const String name = 'BlackListRoute';
@ -174,7 +199,10 @@ class BlackListRoute extends _i41.PageRouteInfo<void> {
/// [_i5.ConnectControlPage] /// [_i5.ConnectControlPage]
class ConnectControlRoute extends _i41.PageRouteInfo<void> { class ConnectControlRoute extends _i41.PageRouteInfo<void> {
const ConnectControlRoute({List<_i41.PageRouteInfo>? children}) const ConnectControlRoute({List<_i41.PageRouteInfo>? children})
: super(ConnectControlRoute.name, initialChildren: children); : super(
ConnectControlRoute.name,
initialChildren: children,
);
static const String name = 'ConnectControlRoute'; static const String name = 'ConnectControlRoute';
@ -190,7 +218,10 @@ class ConnectControlRoute extends _i41.PageRouteInfo<void> {
/// [_i6.ConnectPage] /// [_i6.ConnectPage]
class ConnectRoute extends _i41.PageRouteInfo<void> { class ConnectRoute extends _i41.PageRouteInfo<void> {
const ConnectRoute({List<_i41.PageRouteInfo>? children}) const ConnectRoute({List<_i41.PageRouteInfo>? children})
: super(ConnectRoute.name, initialChildren: children); : super(
ConnectRoute.name,
initialChildren: children,
);
static const String name = 'ConnectRoute'; static const String name = 'ConnectRoute';
@ -206,7 +237,10 @@ class ConnectRoute extends _i41.PageRouteInfo<void> {
/// [_i7.GettingStartedPage] /// [_i7.GettingStartedPage]
class GettingStartedRoute extends _i41.PageRouteInfo<void> { class GettingStartedRoute extends _i41.PageRouteInfo<void> {
const GettingStartedRoute({List<_i41.PageRouteInfo>? children}) const GettingStartedRoute({List<_i41.PageRouteInfo>? children})
: super(GettingStartedRoute.name, initialChildren: children); : super(
GettingStartedRoute.name,
initialChildren: children,
);
static const String name = 'GettingStartedRoute'; static const String name = 'GettingStartedRoute';
@ -228,15 +262,15 @@ class HomeBrowseSectionItemsRoute
required _i43.SpotubeBrowseSectionObject<Object> section, required _i43.SpotubeBrowseSectionObject<Object> section,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
HomeBrowseSectionItemsRoute.name, HomeBrowseSectionItemsRoute.name,
args: HomeBrowseSectionItemsRouteArgs( args: HomeBrowseSectionItemsRouteArgs(
key: key, key: key,
sectionId: sectionId, sectionId: sectionId,
section: section, section: section,
), ),
rawPathParams: {'sectionId': sectionId}, rawPathParams: {'sectionId': sectionId},
initialChildren: children, initialChildren: children,
); );
static const String name = 'HomeBrowseSectionItemsRoute'; static const String name = 'HomeBrowseSectionItemsRoute';
@ -276,7 +310,10 @@ class HomeBrowseSectionItemsRouteArgs {
/// [_i9.HomePage] /// [_i9.HomePage]
class HomeRoute extends _i41.PageRouteInfo<void> { class HomeRoute extends _i41.PageRouteInfo<void> {
const HomeRoute({List<_i41.PageRouteInfo>? children}) const HomeRoute({List<_i41.PageRouteInfo>? children})
: super(HomeRoute.name, initialChildren: children); : super(
HomeRoute.name,
initialChildren: children,
);
static const String name = 'HomeRoute'; static const String name = 'HomeRoute';
@ -292,7 +329,10 @@ class HomeRoute extends _i41.PageRouteInfo<void> {
/// [_i10.LastFMLoginPage] /// [_i10.LastFMLoginPage]
class LastFMLoginRoute extends _i41.PageRouteInfo<void> { class LastFMLoginRoute extends _i41.PageRouteInfo<void> {
const LastFMLoginRoute({List<_i41.PageRouteInfo>? children}) const LastFMLoginRoute({List<_i41.PageRouteInfo>? children})
: super(LastFMLoginRoute.name, initialChildren: children); : super(
LastFMLoginRoute.name,
initialChildren: children,
);
static const String name = 'LastFMLoginRoute'; static const String name = 'LastFMLoginRoute';
@ -308,7 +348,10 @@ class LastFMLoginRoute extends _i41.PageRouteInfo<void> {
/// [_i11.LibraryPage] /// [_i11.LibraryPage]
class LibraryRoute extends _i41.PageRouteInfo<void> { class LibraryRoute extends _i41.PageRouteInfo<void> {
const LibraryRoute({List<_i41.PageRouteInfo>? children}) const LibraryRoute({List<_i41.PageRouteInfo>? children})
: super(LibraryRoute.name, initialChildren: children); : super(
LibraryRoute.name,
initialChildren: children,
);
static const String name = 'LibraryRoute'; static const String name = 'LibraryRoute';
@ -328,10 +371,13 @@ class LikedPlaylistRoute extends _i41.PageRouteInfo<LikedPlaylistRouteArgs> {
required _i43.SpotubeSimplePlaylistObject playlist, required _i43.SpotubeSimplePlaylistObject playlist,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
LikedPlaylistRoute.name, LikedPlaylistRoute.name,
args: LikedPlaylistRouteArgs(key: key, playlist: playlist), args: LikedPlaylistRouteArgs(
initialChildren: children, key: key,
); playlist: playlist,
),
initialChildren: children,
);
static const String name = 'LikedPlaylistRoute'; static const String name = 'LikedPlaylistRoute';
@ -339,13 +385,19 @@ class LikedPlaylistRoute extends _i41.PageRouteInfo<LikedPlaylistRouteArgs> {
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<LikedPlaylistRouteArgs>(); final args = data.argsAs<LikedPlaylistRouteArgs>();
return _i12.LikedPlaylistPage(key: args.key, playlist: args.playlist); return _i12.LikedPlaylistPage(
key: args.key,
playlist: args.playlist,
);
}, },
); );
} }
class LikedPlaylistRouteArgs { class LikedPlaylistRouteArgs {
const LikedPlaylistRouteArgs({this.key, required this.playlist}); const LikedPlaylistRouteArgs({
this.key,
required this.playlist,
});
final _i42.Key? key; final _i42.Key? key;
@ -367,15 +419,15 @@ class LocalLibraryRoute extends _i41.PageRouteInfo<LocalLibraryRouteArgs> {
bool isCache = false, bool isCache = false,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
LocalLibraryRoute.name, LocalLibraryRoute.name,
args: LocalLibraryRouteArgs( args: LocalLibraryRouteArgs(
location: location, location: location,
key: key, key: key,
isDownloads: isDownloads, isDownloads: isDownloads,
isCache: isCache, isCache: isCache,
), ),
initialChildren: children, initialChildren: children,
); );
static const String name = 'LocalLibraryRoute'; static const String name = 'LocalLibraryRoute';
@ -419,7 +471,10 @@ class LocalLibraryRouteArgs {
/// [_i14.LogsPage] /// [_i14.LogsPage]
class LogsRoute extends _i41.PageRouteInfo<void> { class LogsRoute extends _i41.PageRouteInfo<void> {
const LogsRoute({List<_i41.PageRouteInfo>? children}) const LogsRoute({List<_i41.PageRouteInfo>? children})
: super(LogsRoute.name, initialChildren: children); : super(
LogsRoute.name,
initialChildren: children,
);
static const String name = 'LogsRoute'; static const String name = 'LogsRoute';
@ -435,7 +490,10 @@ class LogsRoute extends _i41.PageRouteInfo<void> {
/// [_i15.LyricsPage] /// [_i15.LyricsPage]
class LyricsRoute extends _i41.PageRouteInfo<void> { class LyricsRoute extends _i41.PageRouteInfo<void> {
const LyricsRoute({List<_i41.PageRouteInfo>? children}) const LyricsRoute({List<_i41.PageRouteInfo>? children})
: super(LyricsRoute.name, initialChildren: children); : super(
LyricsRoute.name,
initialChildren: children,
);
static const String name = 'LyricsRoute'; static const String name = 'LyricsRoute';
@ -455,10 +513,13 @@ class MiniLyricsRoute extends _i41.PageRouteInfo<MiniLyricsRouteArgs> {
required _i44.Size prevSize, required _i44.Size prevSize,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
MiniLyricsRoute.name, MiniLyricsRoute.name,
args: MiniLyricsRouteArgs(key: key, prevSize: prevSize), args: MiniLyricsRouteArgs(
initialChildren: children, key: key,
); prevSize: prevSize,
),
initialChildren: children,
);
static const String name = 'MiniLyricsRoute'; static const String name = 'MiniLyricsRoute';
@ -466,13 +527,19 @@ class MiniLyricsRoute extends _i41.PageRouteInfo<MiniLyricsRouteArgs> {
name, name,
builder: (data) { builder: (data) {
final args = data.argsAs<MiniLyricsRouteArgs>(); final args = data.argsAs<MiniLyricsRouteArgs>();
return _i16.MiniLyricsPage(key: args.key, prevSize: args.prevSize); return _i16.MiniLyricsPage(
key: args.key,
prevSize: args.prevSize,
);
}, },
); );
} }
class MiniLyricsRouteArgs { class MiniLyricsRouteArgs {
const MiniLyricsRouteArgs({this.key, required this.prevSize}); const MiniLyricsRouteArgs({
this.key,
required this.prevSize,
});
final _i44.Key? key; final _i44.Key? key;
@ -488,7 +555,10 @@ class MiniLyricsRouteArgs {
/// [_i17.PlayerLyricsPage] /// [_i17.PlayerLyricsPage]
class PlayerLyricsRoute extends _i41.PageRouteInfo<void> { class PlayerLyricsRoute extends _i41.PageRouteInfo<void> {
const PlayerLyricsRoute({List<_i41.PageRouteInfo>? children}) const PlayerLyricsRoute({List<_i41.PageRouteInfo>? children})
: super(PlayerLyricsRoute.name, initialChildren: children); : super(
PlayerLyricsRoute.name,
initialChildren: children,
);
static const String name = 'PlayerLyricsRoute'; static const String name = 'PlayerLyricsRoute';
@ -504,7 +574,10 @@ class PlayerLyricsRoute extends _i41.PageRouteInfo<void> {
/// [_i18.PlayerQueuePage] /// [_i18.PlayerQueuePage]
class PlayerQueueRoute extends _i41.PageRouteInfo<void> { class PlayerQueueRoute extends _i41.PageRouteInfo<void> {
const PlayerQueueRoute({List<_i41.PageRouteInfo>? children}) const PlayerQueueRoute({List<_i41.PageRouteInfo>? children})
: super(PlayerQueueRoute.name, initialChildren: children); : super(
PlayerQueueRoute.name,
initialChildren: children,
);
static const String name = 'PlayerQueueRoute'; static const String name = 'PlayerQueueRoute';
@ -520,7 +593,10 @@ class PlayerQueueRoute extends _i41.PageRouteInfo<void> {
/// [_i19.PlayerTrackSourcesPage] /// [_i19.PlayerTrackSourcesPage]
class PlayerTrackSourcesRoute extends _i41.PageRouteInfo<void> { class PlayerTrackSourcesRoute extends _i41.PageRouteInfo<void> {
const PlayerTrackSourcesRoute({List<_i41.PageRouteInfo>? children}) const PlayerTrackSourcesRoute({List<_i41.PageRouteInfo>? children})
: super(PlayerTrackSourcesRoute.name, initialChildren: children); : super(
PlayerTrackSourcesRoute.name,
initialChildren: children,
);
static const String name = 'PlayerTrackSourcesRoute'; static const String name = 'PlayerTrackSourcesRoute';
@ -541,11 +617,15 @@ class PlaylistRoute extends _i41.PageRouteInfo<PlaylistRouteArgs> {
required _i43.SpotubeSimplePlaylistObject playlist, required _i43.SpotubeSimplePlaylistObject playlist,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
PlaylistRoute.name, PlaylistRoute.name,
args: PlaylistRouteArgs(key: key, id: id, playlist: playlist), args: PlaylistRouteArgs(
rawPathParams: {'id': id}, key: key,
initialChildren: children, id: id,
); playlist: playlist,
),
rawPathParams: {'id': id},
initialChildren: children,
);
static const String name = 'PlaylistRoute'; static const String name = 'PlaylistRoute';
@ -563,7 +643,11 @@ class PlaylistRoute extends _i41.PageRouteInfo<PlaylistRouteArgs> {
} }
class PlaylistRouteArgs { class PlaylistRouteArgs {
const PlaylistRouteArgs({this.key, required this.id, required this.playlist}); const PlaylistRouteArgs({
this.key,
required this.id,
required this.playlist,
});
final _i42.Key? key; final _i42.Key? key;
@ -581,7 +665,10 @@ class PlaylistRouteArgs {
/// [_i21.ProfilePage] /// [_i21.ProfilePage]
class ProfileRoute extends _i41.PageRouteInfo<void> { class ProfileRoute extends _i41.PageRouteInfo<void> {
const ProfileRoute({List<_i41.PageRouteInfo>? children}) const ProfileRoute({List<_i41.PageRouteInfo>? children})
: super(ProfileRoute.name, initialChildren: children); : super(
ProfileRoute.name,
initialChildren: children,
);
static const String name = 'ProfileRoute'; static const String name = 'ProfileRoute';
@ -597,7 +684,10 @@ class ProfileRoute extends _i41.PageRouteInfo<void> {
/// [_i22.RootAppPage] /// [_i22.RootAppPage]
class RootAppRoute extends _i41.PageRouteInfo<void> { class RootAppRoute extends _i41.PageRouteInfo<void> {
const RootAppRoute({List<_i41.PageRouteInfo>? children}) const RootAppRoute({List<_i41.PageRouteInfo>? children})
: super(RootAppRoute.name, initialChildren: children); : super(
RootAppRoute.name,
initialChildren: children,
);
static const String name = 'RootAppRoute'; static const String name = 'RootAppRoute';
@ -613,7 +703,10 @@ class RootAppRoute extends _i41.PageRouteInfo<void> {
/// [_i23.SearchPage] /// [_i23.SearchPage]
class SearchRoute extends _i41.PageRouteInfo<void> { class SearchRoute extends _i41.PageRouteInfo<void> {
const SearchRoute({List<_i41.PageRouteInfo>? children}) const SearchRoute({List<_i41.PageRouteInfo>? children})
: super(SearchRoute.name, initialChildren: children); : super(
SearchRoute.name,
initialChildren: children,
);
static const String name = 'SearchRoute'; static const String name = 'SearchRoute';
@ -635,14 +728,14 @@ class SettingsMetadataProviderFormRoute
required List<_i43.MetadataFormFieldObject> fields, required List<_i43.MetadataFormFieldObject> fields,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
SettingsMetadataProviderFormRoute.name, SettingsMetadataProviderFormRoute.name,
args: SettingsMetadataProviderFormRouteArgs( args: SettingsMetadataProviderFormRouteArgs(
key: key, key: key,
title: title, title: title,
fields: fields, fields: fields,
), ),
initialChildren: children, initialChildren: children,
); );
static const String name = 'SettingsMetadataProviderFormRoute'; static const String name = 'SettingsMetadataProviderFormRoute';
@ -682,7 +775,10 @@ class SettingsMetadataProviderFormRouteArgs {
/// [_i25.SettingsMetadataProviderPage] /// [_i25.SettingsMetadataProviderPage]
class SettingsMetadataProviderRoute extends _i41.PageRouteInfo<void> { class SettingsMetadataProviderRoute extends _i41.PageRouteInfo<void> {
const SettingsMetadataProviderRoute({List<_i41.PageRouteInfo>? children}) const SettingsMetadataProviderRoute({List<_i41.PageRouteInfo>? children})
: super(SettingsMetadataProviderRoute.name, initialChildren: children); : super(
SettingsMetadataProviderRoute.name,
initialChildren: children,
);
static const String name = 'SettingsMetadataProviderRoute'; static const String name = 'SettingsMetadataProviderRoute';
@ -698,7 +794,10 @@ class SettingsMetadataProviderRoute extends _i41.PageRouteInfo<void> {
/// [_i26.SettingsPage] /// [_i26.SettingsPage]
class SettingsRoute extends _i41.PageRouteInfo<void> { class SettingsRoute extends _i41.PageRouteInfo<void> {
const SettingsRoute({List<_i41.PageRouteInfo>? children}) const SettingsRoute({List<_i41.PageRouteInfo>? children})
: super(SettingsRoute.name, initialChildren: children); : super(
SettingsRoute.name,
initialChildren: children,
);
static const String name = 'SettingsRoute'; static const String name = 'SettingsRoute';
@ -714,7 +813,10 @@ class SettingsRoute extends _i41.PageRouteInfo<void> {
/// [_i27.SettingsScrobblingPage] /// [_i27.SettingsScrobblingPage]
class SettingsScrobblingRoute extends _i41.PageRouteInfo<void> { class SettingsScrobblingRoute extends _i41.PageRouteInfo<void> {
const SettingsScrobblingRoute({List<_i41.PageRouteInfo>? children}) const SettingsScrobblingRoute({List<_i41.PageRouteInfo>? children})
: super(SettingsScrobblingRoute.name, initialChildren: children); : super(
SettingsScrobblingRoute.name,
initialChildren: children,
);
static const String name = 'SettingsScrobblingRoute'; static const String name = 'SettingsScrobblingRoute';
@ -730,7 +832,10 @@ class SettingsScrobblingRoute extends _i41.PageRouteInfo<void> {
/// [_i28.StatsAlbumsPage] /// [_i28.StatsAlbumsPage]
class StatsAlbumsRoute extends _i41.PageRouteInfo<void> { class StatsAlbumsRoute extends _i41.PageRouteInfo<void> {
const StatsAlbumsRoute({List<_i41.PageRouteInfo>? children}) const StatsAlbumsRoute({List<_i41.PageRouteInfo>? children})
: super(StatsAlbumsRoute.name, initialChildren: children); : super(
StatsAlbumsRoute.name,
initialChildren: children,
);
static const String name = 'StatsAlbumsRoute'; static const String name = 'StatsAlbumsRoute';
@ -746,7 +851,10 @@ class StatsAlbumsRoute extends _i41.PageRouteInfo<void> {
/// [_i29.StatsArtistsPage] /// [_i29.StatsArtistsPage]
class StatsArtistsRoute extends _i41.PageRouteInfo<void> { class StatsArtistsRoute extends _i41.PageRouteInfo<void> {
const StatsArtistsRoute({List<_i41.PageRouteInfo>? children}) const StatsArtistsRoute({List<_i41.PageRouteInfo>? children})
: super(StatsArtistsRoute.name, initialChildren: children); : super(
StatsArtistsRoute.name,
initialChildren: children,
);
static const String name = 'StatsArtistsRoute'; static const String name = 'StatsArtistsRoute';
@ -762,7 +870,10 @@ class StatsArtistsRoute extends _i41.PageRouteInfo<void> {
/// [_i30.StatsMinutesPage] /// [_i30.StatsMinutesPage]
class StatsMinutesRoute extends _i41.PageRouteInfo<void> { class StatsMinutesRoute extends _i41.PageRouteInfo<void> {
const StatsMinutesRoute({List<_i41.PageRouteInfo>? children}) const StatsMinutesRoute({List<_i41.PageRouteInfo>? children})
: super(StatsMinutesRoute.name, initialChildren: children); : super(
StatsMinutesRoute.name,
initialChildren: children,
);
static const String name = 'StatsMinutesRoute'; static const String name = 'StatsMinutesRoute';
@ -778,7 +889,10 @@ class StatsMinutesRoute extends _i41.PageRouteInfo<void> {
/// [_i31.StatsPage] /// [_i31.StatsPage]
class StatsRoute extends _i41.PageRouteInfo<void> { class StatsRoute extends _i41.PageRouteInfo<void> {
const StatsRoute({List<_i41.PageRouteInfo>? children}) const StatsRoute({List<_i41.PageRouteInfo>? children})
: super(StatsRoute.name, initialChildren: children); : super(
StatsRoute.name,
initialChildren: children,
);
static const String name = 'StatsRoute'; static const String name = 'StatsRoute';
@ -794,7 +908,10 @@ class StatsRoute extends _i41.PageRouteInfo<void> {
/// [_i32.StatsPlaylistsPage] /// [_i32.StatsPlaylistsPage]
class StatsPlaylistsRoute extends _i41.PageRouteInfo<void> { class StatsPlaylistsRoute extends _i41.PageRouteInfo<void> {
const StatsPlaylistsRoute({List<_i41.PageRouteInfo>? children}) const StatsPlaylistsRoute({List<_i41.PageRouteInfo>? children})
: super(StatsPlaylistsRoute.name, initialChildren: children); : super(
StatsPlaylistsRoute.name,
initialChildren: children,
);
static const String name = 'StatsPlaylistsRoute'; static const String name = 'StatsPlaylistsRoute';
@ -810,7 +927,10 @@ class StatsPlaylistsRoute extends _i41.PageRouteInfo<void> {
/// [_i33.StatsStreamFeesPage] /// [_i33.StatsStreamFeesPage]
class StatsStreamFeesRoute extends _i41.PageRouteInfo<void> { class StatsStreamFeesRoute extends _i41.PageRouteInfo<void> {
const StatsStreamFeesRoute({List<_i41.PageRouteInfo>? children}) const StatsStreamFeesRoute({List<_i41.PageRouteInfo>? children})
: super(StatsStreamFeesRoute.name, initialChildren: children); : super(
StatsStreamFeesRoute.name,
initialChildren: children,
);
static const String name = 'StatsStreamFeesRoute'; static const String name = 'StatsStreamFeesRoute';
@ -826,7 +946,10 @@ class StatsStreamFeesRoute extends _i41.PageRouteInfo<void> {
/// [_i34.StatsStreamsPage] /// [_i34.StatsStreamsPage]
class StatsStreamsRoute extends _i41.PageRouteInfo<void> { class StatsStreamsRoute extends _i41.PageRouteInfo<void> {
const StatsStreamsRoute({List<_i41.PageRouteInfo>? children}) const StatsStreamsRoute({List<_i41.PageRouteInfo>? children})
: super(StatsStreamsRoute.name, initialChildren: children); : super(
StatsStreamsRoute.name,
initialChildren: children,
);
static const String name = 'StatsStreamsRoute'; static const String name = 'StatsStreamsRoute';
@ -846,11 +969,14 @@ class TrackRoute extends _i41.PageRouteInfo<TrackRouteArgs> {
required String trackId, required String trackId,
List<_i41.PageRouteInfo>? children, List<_i41.PageRouteInfo>? children,
}) : super( }) : super(
TrackRoute.name, TrackRoute.name,
args: TrackRouteArgs(key: key, trackId: trackId), args: TrackRouteArgs(
rawPathParams: {'id': trackId}, key: key,
initialChildren: children, trackId: trackId,
); ),
rawPathParams: {'id': trackId},
initialChildren: children,
);
static const String name = 'TrackRoute'; static const String name = 'TrackRoute';
@ -859,15 +985,20 @@ class TrackRoute extends _i41.PageRouteInfo<TrackRouteArgs> {
builder: (data) { builder: (data) {
final pathParams = data.inheritedPathParams; final pathParams = data.inheritedPathParams;
final args = data.argsAs<TrackRouteArgs>( final args = data.argsAs<TrackRouteArgs>(
orElse: () => TrackRouteArgs(trackId: pathParams.getString('id')), orElse: () => TrackRouteArgs(trackId: pathParams.getString('id')));
return _i35.TrackPage(
key: args.key,
trackId: args.trackId,
); );
return _i35.TrackPage(key: args.key, trackId: args.trackId);
}, },
); );
} }
class TrackRouteArgs { class TrackRouteArgs {
const TrackRouteArgs({this.key, required this.trackId}); const TrackRouteArgs({
this.key,
required this.trackId,
});
final _i44.Key? key; final _i44.Key? key;
@ -883,7 +1014,10 @@ class TrackRouteArgs {
/// [_i36.UserAlbumsPage] /// [_i36.UserAlbumsPage]
class UserAlbumsRoute extends _i41.PageRouteInfo<void> { class UserAlbumsRoute extends _i41.PageRouteInfo<void> {
const UserAlbumsRoute({List<_i41.PageRouteInfo>? children}) const UserAlbumsRoute({List<_i41.PageRouteInfo>? children})
: super(UserAlbumsRoute.name, initialChildren: children); : super(
UserAlbumsRoute.name,
initialChildren: children,
);
static const String name = 'UserAlbumsRoute'; static const String name = 'UserAlbumsRoute';
@ -899,7 +1033,10 @@ class UserAlbumsRoute extends _i41.PageRouteInfo<void> {
/// [_i37.UserArtistsPage] /// [_i37.UserArtistsPage]
class UserArtistsRoute extends _i41.PageRouteInfo<void> { class UserArtistsRoute extends _i41.PageRouteInfo<void> {
const UserArtistsRoute({List<_i41.PageRouteInfo>? children}) const UserArtistsRoute({List<_i41.PageRouteInfo>? children})
: super(UserArtistsRoute.name, initialChildren: children); : super(
UserArtistsRoute.name,
initialChildren: children,
);
static const String name = 'UserArtistsRoute'; static const String name = 'UserArtistsRoute';
@ -915,7 +1052,10 @@ class UserArtistsRoute extends _i41.PageRouteInfo<void> {
/// [_i38.UserDownloadsPage] /// [_i38.UserDownloadsPage]
class UserDownloadsRoute extends _i41.PageRouteInfo<void> { class UserDownloadsRoute extends _i41.PageRouteInfo<void> {
const UserDownloadsRoute({List<_i41.PageRouteInfo>? children}) const UserDownloadsRoute({List<_i41.PageRouteInfo>? children})
: super(UserDownloadsRoute.name, initialChildren: children); : super(
UserDownloadsRoute.name,
initialChildren: children,
);
static const String name = 'UserDownloadsRoute'; static const String name = 'UserDownloadsRoute';
@ -931,7 +1071,10 @@ class UserDownloadsRoute extends _i41.PageRouteInfo<void> {
/// [_i39.UserLocalLibraryPage] /// [_i39.UserLocalLibraryPage]
class UserLocalLibraryRoute extends _i41.PageRouteInfo<void> { class UserLocalLibraryRoute extends _i41.PageRouteInfo<void> {
const UserLocalLibraryRoute({List<_i41.PageRouteInfo>? children}) const UserLocalLibraryRoute({List<_i41.PageRouteInfo>? children})
: super(UserLocalLibraryRoute.name, initialChildren: children); : super(
UserLocalLibraryRoute.name,
initialChildren: children,
);
static const String name = 'UserLocalLibraryRoute'; static const String name = 'UserLocalLibraryRoute';
@ -947,7 +1090,10 @@ class UserLocalLibraryRoute extends _i41.PageRouteInfo<void> {
/// [_i40.UserPlaylistsPage] /// [_i40.UserPlaylistsPage]
class UserPlaylistsRoute extends _i41.PageRouteInfo<void> { class UserPlaylistsRoute extends _i41.PageRouteInfo<void> {
const UserPlaylistsRoute({List<_i41.PageRouteInfo>? children}) const UserPlaylistsRoute({List<_i41.PageRouteInfo>? children})
: super(UserPlaylistsRoute.name, initialChildren: children); : super(
UserPlaylistsRoute.name,
initialChildren: children,
);
static const String name = 'UserPlaylistsRoute'; static const String name = 'UserPlaylistsRoute';

View File

@ -80,7 +80,6 @@ abstract class SpotubeIcons {
static const hoverOff = Icons.back_hand_outlined; static const hoverOff = Icons.back_hand_outlined;
static const dragHandle = Icons.drag_indicator; static const dragHandle = Icons.drag_indicator;
static const lightning = Icons.flash_on_rounded; static const lightning = Icons.flash_on_rounded;
static const lightningOutlined = FeatherIcons.zap;
static const colorSync = FeatherIcons.activity; static const colorSync = FeatherIcons.activity;
static const language = FeatherIcons.globe; static const language = FeatherIcons.globe;
static const error = FeatherIcons.alertTriangle; static const error = FeatherIcons.alertTriangle;

View File

@ -1,69 +0,0 @@
import 'package:flutter/services.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
import 'package:url_launcher/url_launcher_string.dart';
class LinkOpenPermissionDialog extends StatelessWidget {
final String? href;
const LinkOpenPermissionDialog({super.key, this.href});
@override
Widget build(BuildContext context) {
return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: AlertDialog(
title: Row(
spacing: 8,
children: [
const Icon(SpotubeIcons.warning),
Text(context.l10n.open_link_in_browser),
],
),
content: Text.rich(
TextSpan(
children: [
TextSpan(
text:
"${context.l10n.do_you_want_to_open_the_following_link}:\n",
),
if (href != null)
TextSpan(
text: "$href\n\n",
style: const TextStyle(color: Colors.blue),
),
TextSpan(text: context.l10n.unsafe_url_warning),
],
),
),
actions: [
Button.ghost(
onPressed: () => Navigator.of(context).pop(false),
child: Text(context.l10n.cancel),
),
Button.ghost(
onPressed: () {
if (href != null) {
Clipboard.setData(ClipboardData(text: href!));
}
Navigator.of(context).pop(false);
},
child: Text(context.l10n.copy_link),
),
Button.destructive(
onPressed: () {
if (href != null) {
launchUrlString(
href!,
mode: LaunchMode.externalApplication,
);
}
Navigator.of(context).pop(true);
},
child: Text(context.l10n.open),
),
],
),
);
}
}

View File

@ -1,7 +1,9 @@
import 'package:cached_network_image/cached_network_image.dart'; import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart'; import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
import 'package:spotube/components/dialogs/link_open_permission_dialog.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/extensions/context.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
class AppMarkdown extends StatelessWidget { class AppMarkdown extends StatelessWidget {
@ -26,7 +28,61 @@ class AppMarkdown extends StatelessWidget {
final allowOpeningLink = await showDialog<bool>( final allowOpeningLink = await showDialog<bool>(
context: context, context: context,
builder: (context) { builder: (context) {
return LinkOpenPermissionDialog(href: href); return ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 450),
child: AlertDialog(
title: Row(
spacing: 8,
children: [
const Icon(SpotubeIcons.warning),
Text(context.l10n.open_link_in_browser),
],
),
content: Text.rich(
TextSpan(
children: [
TextSpan(
text:
"${context.l10n.do_you_want_to_open_the_following_link}:\n",
),
if (href != null)
TextSpan(
text: "$href\n\n",
style: const TextStyle(color: Colors.blue),
),
TextSpan(text: context.l10n.unsafe_url_warning),
],
),
),
actions: [
Button.ghost(
onPressed: () => Navigator.of(context).pop(false),
child: Text(context.l10n.cancel),
),
Button.ghost(
onPressed: () {
if (href != null) {
Clipboard.setData(ClipboardData(text: href));
}
Navigator.of(context).pop(false);
},
child: Text(context.l10n.copy_link),
),
Button.destructive(
onPressed: () {
if (href != null) {
launchUrlString(
href,
mode: LaunchMode.externalApplication,
);
}
Navigator.of(context).pop(true);
},
child: Text(context.l10n.open),
),
],
),
);
}, },
); );

View File

@ -5,7 +5,6 @@ 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/assets.gen.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';
import 'package:spotube/extensions/constrains.dart'; import 'package:spotube/extensions/constrains.dart';
@ -60,7 +59,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.delete, TrackOptionValue.delete,
playlistId, playlistId,
); );
@ -74,7 +73,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.album, TrackOptionValue.album,
playlistId, playlistId,
); );
@ -98,7 +97,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.addToQueue, TrackOptionValue.addToQueue,
playlistId, playlistId,
); );
@ -111,7 +110,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.playNext, TrackOptionValue.playNext,
playlistId, playlistId,
); );
@ -125,7 +124,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.removeFromQueue, TrackOptionValue.removeFromQueue,
playlistId, playlistId,
); );
@ -140,7 +139,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.favorite, TrackOptionValue.favorite,
playlistId, playlistId,
); );
@ -163,7 +162,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.startRadio, TrackOptionValue.startRadio,
playlistId, playlistId,
); );
@ -176,7 +175,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.addToPlaylist, TrackOptionValue.addToPlaylist,
playlistId, playlistId,
); );
@ -191,7 +190,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.removeFromPlaylist, TrackOptionValue.removeFromPlaylist,
playlistId, playlistId,
); );
@ -205,7 +204,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.download, TrackOptionValue.download,
playlistId, playlistId,
); );
@ -227,7 +226,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.blacklist, TrackOptionValue.blacklist,
playlistId, playlistId,
); );
@ -251,7 +250,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.share, TrackOptionValue.share,
playlistId, playlistId,
); );
@ -265,7 +264,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.songlink, TrackOptionValue.songlink,
playlistId, playlistId,
); );
@ -283,7 +282,7 @@ class TrackOptions extends HookConsumerWidget {
style: ButtonVariance.menu, style: ButtonVariance.menu,
onPressed: () async { onPressed: () async {
await trackOptionActions.action( await trackOptionActions.action(
rootNavigatorKey.currentContext!, context,
TrackOptionValue.details, TrackOptionValue.details,
playlistId, playlistId,
); );

View File

@ -461,8 +461,5 @@
"available_plugins": "Available plugins", "available_plugins": "Available plugins",
"configure_your_own_metadata_plugin": "Configure your own playlist/album/artist/feed metadata provider", "configure_your_own_metadata_plugin": "Configure your own playlist/album/artist/feed metadata provider",
"audio_scrobblers": "Audio Scrobblers", "audio_scrobblers": "Audio Scrobblers",
"scrobbling": "Scrobbling", "scrobbling": "Scrobbling"
"source": "Source: ",
"uncompressed": "Uncompressed",
"dab_music_source_description": "For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching."
} }

View File

@ -2930,24 +2930,6 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Scrobbling'** /// **'Scrobbling'**
String get scrobbling; String get scrobbling;
/// No description provided for @source.
///
/// In en, this message translates to:
/// **'Source: '**
String get source;
/// No description provided for @uncompressed.
///
/// In en, this message translates to:
/// **'Uncompressed'**
String get uncompressed;
/// No description provided for @dab_music_source_description.
///
/// In en, this message translates to:
/// **'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.'**
String get dab_music_source_description;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@ -1537,14 +1537,4 @@ class AppLocalizationsAr extends AppLocalizations {
@override @override
String get scrobbling => 'التتبع'; String get scrobbling => 'التتبع';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1538,14 +1538,4 @@ class AppLocalizationsBn extends AppLocalizations {
@override @override
String get scrobbling => 'স্ক্রোব্বলিং'; String get scrobbling => 'স্ক্রোব্বলিং';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1548,14 +1548,4 @@ class AppLocalizationsCa extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1538,14 +1538,4 @@ class AppLocalizationsCs extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1550,14 +1550,4 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1536,14 +1536,4 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1551,14 +1551,4 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1548,14 +1548,4 @@ class AppLocalizationsEu extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1536,14 +1536,4 @@ class AppLocalizationsFa extends AppLocalizations {
@override @override
String get scrobbling => 'اسکراب‌بلینگ'; String get scrobbling => 'اسکراب‌بلینگ';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1536,14 +1536,4 @@ class AppLocalizationsFi extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1556,14 +1556,4 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1542,14 +1542,4 @@ class AppLocalizationsHi extends AppLocalizations {
@override @override
String get scrobbling => 'स्क्रॉबलिंग'; String get scrobbling => 'स्क्रॉबलिंग';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1544,14 +1544,4 @@ class AppLocalizationsId extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1543,14 +1543,4 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1507,14 +1507,4 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1545,14 +1545,4 @@ class AppLocalizationsKa extends AppLocalizations {
@override @override
String get scrobbling => 'სქრობლინგი'; String get scrobbling => 'სქრობლინგი';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1511,14 +1511,4 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get scrobbling => '스크로블링'; String get scrobbling => '스크로블링';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1548,14 +1548,4 @@ class AppLocalizationsNe extends AppLocalizations {
@override @override
String get scrobbling => 'स्क्रब्बलिंग'; String get scrobbling => 'स्क्रब्बलिंग';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1542,14 +1542,4 @@ class AppLocalizationsNl extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1544,14 +1544,4 @@ class AppLocalizationsPl extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1541,14 +1541,4 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1544,14 +1544,4 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get scrobbling => 'Скробблинг'; String get scrobbling => 'Скробблинг';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1550,14 +1550,4 @@ class AppLocalizationsTa extends AppLocalizations {
@override @override
String get scrobbling => 'ஸ்க்ரோப்ளிங்'; String get scrobbling => 'ஸ்க்ரோப்ளிங்';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1533,14 +1533,4 @@ class AppLocalizationsTh extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1551,14 +1551,4 @@ class AppLocalizationsTl extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1544,14 +1544,4 @@ class AppLocalizationsTr extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1540,14 +1540,4 @@ class AppLocalizationsUk extends AppLocalizations {
@override @override
String get scrobbling => 'Скроблінг'; String get scrobbling => 'Скроблінг';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1546,14 +1546,4 @@ class AppLocalizationsVi extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }

View File

@ -1500,16 +1500,6 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get scrobbling => 'Scrobbling'; String get scrobbling => 'Scrobbling';
@override
String get source => 'Source: ';
@override
String get uncompressed => 'Uncompressed';
@override
String get dab_music_source_description =>
'For audiophiles. Provides high-quality/lossless audio streams. Accurate ISRC based track matching.';
} }
/// The translations for Chinese, as used in Taiwan (`zh_TW`). /// The translations for Chinese, as used in Taiwan (`zh_TW`).

View File

@ -112,13 +112,8 @@ mixin _$WebSocketLoadEventData {
required TResult orElse(), required TResult orElse(),
}) => }) =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
/// Serializes this WebSocketLoadEventData to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$WebSocketLoadEventDataCopyWith<WebSocketLoadEventData> get copyWith => $WebSocketLoadEventDataCopyWith<WebSocketLoadEventData> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -147,8 +142,6 @@ class _$WebSocketLoadEventDataCopyWithImpl<$Res,
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; final $Res Function($Val) _then;
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -197,8 +190,6 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res>
$Res Function(_$WebSocketLoadEventDataPlaylistImpl) _then) $Res Function(_$WebSocketLoadEventDataPlaylistImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -222,8 +213,6 @@ class __$$WebSocketLoadEventDataPlaylistImplCopyWithImpl<$Res>
)); ));
} }
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SpotubeSimplePlaylistObjectCopyWith<$Res>? get collection { $SpotubeSimplePlaylistObjectCopyWith<$Res>? get collection {
@ -292,14 +281,12 @@ class _$WebSocketLoadEventDataPlaylistImpl
other.initialIndex == initialIndex)); other.initialIndex == initialIndex));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_tracks), collection, initialIndex); const DeepCollectionEquality().hash(_tracks), collection, initialIndex);
/// Create a copy of WebSocketLoadEventData @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$WebSocketLoadEventDataPlaylistImplCopyWith< _$$WebSocketLoadEventDataPlaylistImplCopyWith<
@ -433,11 +420,8 @@ abstract class WebSocketLoadEventDataPlaylist extends WebSocketLoadEventData {
SpotubeSimplePlaylistObject? get collection; SpotubeSimplePlaylistObject? get collection;
@override @override
int? get initialIndex; int? get initialIndex;
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$WebSocketLoadEventDataPlaylistImplCopyWith< _$$WebSocketLoadEventDataPlaylistImplCopyWith<
_$WebSocketLoadEventDataPlaylistImpl> _$WebSocketLoadEventDataPlaylistImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
@ -472,8 +456,6 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res>
$Res Function(_$WebSocketLoadEventDataAlbumImpl) _then) $Res Function(_$WebSocketLoadEventDataAlbumImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -497,8 +479,6 @@ class __$$WebSocketLoadEventDataAlbumImplCopyWithImpl<$Res>
)); ));
} }
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
$SpotubeSimpleAlbumObjectCopyWith<$Res>? get collection { $SpotubeSimpleAlbumObjectCopyWith<$Res>? get collection {
@ -565,14 +545,12 @@ class _$WebSocketLoadEventDataAlbumImpl extends WebSocketLoadEventDataAlbum {
other.initialIndex == initialIndex)); other.initialIndex == initialIndex));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, int get hashCode => Object.hash(runtimeType,
const DeepCollectionEquality().hash(_tracks), collection, initialIndex); const DeepCollectionEquality().hash(_tracks), collection, initialIndex);
/// Create a copy of WebSocketLoadEventData @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl>
@ -705,11 +683,8 @@ abstract class WebSocketLoadEventDataAlbum extends WebSocketLoadEventData {
SpotubeSimpleAlbumObject? get collection; SpotubeSimpleAlbumObject? get collection;
@override @override
int? get initialIndex; int? get initialIndex;
/// Create a copy of WebSocketLoadEventData
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl> _$$WebSocketLoadEventDataAlbumImplCopyWith<_$WebSocketLoadEventDataAlbumImpl>
get copyWith => throw _privateConstructorUsedError; get copyWith => throw _privateConstructorUsedError;
} }

View File

@ -18,12 +18,15 @@ class $AuthenticationTableTable extends AuthenticationTable
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const VerificationMeta _cookieMeta = const VerificationMeta('cookie');
@override @override
late final GeneratedColumnWithTypeConverter<DecryptedText, String> cookie = late final GeneratedColumnWithTypeConverter<DecryptedText, String> cookie =
GeneratedColumn<String>('cookie', aliasedName, false, GeneratedColumn<String>('cookie', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true) type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<DecryptedText>( .withConverter<DecryptedText>(
$AuthenticationTableTable.$convertercookie); $AuthenticationTableTable.$convertercookie);
static const VerificationMeta _accessTokenMeta =
const VerificationMeta('accessToken');
@override @override
late final GeneratedColumnWithTypeConverter<DecryptedText, String> late final GeneratedColumnWithTypeConverter<DecryptedText, String>
accessToken = GeneratedColumn<String>('access_token', aliasedName, false, accessToken = GeneratedColumn<String>('access_token', aliasedName, false,
@ -52,6 +55,8 @@ class $AuthenticationTableTable extends AuthenticationTable
if (data.containsKey('id')) { if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} }
context.handle(_cookieMeta, const VerificationResult.success());
context.handle(_accessTokenMeta, const VerificationResult.success());
if (data.containsKey('expiration')) { if (data.containsKey('expiration')) {
context.handle( context.handle(
_expirationMeta, _expirationMeta,
@ -296,6 +301,8 @@ class $BlacklistTableTable extends BlacklistTable
late final GeneratedColumn<String> name = GeneratedColumn<String>( late final GeneratedColumn<String> name = GeneratedColumn<String>(
'name', aliasedName, false, 'name', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _elementTypeMeta =
const VerificationMeta('elementType');
@override @override
late final GeneratedColumnWithTypeConverter<BlacklistedType, String> late final GeneratedColumnWithTypeConverter<BlacklistedType, String>
elementType = GeneratedColumn<String>('element_type', aliasedName, false, elementType = GeneratedColumn<String>('element_type', aliasedName, false,
@ -329,6 +336,7 @@ class $BlacklistTableTable extends BlacklistTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_nameMeta); context.missing(_nameMeta);
} }
context.handle(_elementTypeMeta, const VerificationResult.success());
if (data.containsKey('element_id')) { if (data.containsKey('element_id')) {
context.handle(_elementIdMeta, context.handle(_elementIdMeta,
elementId.isAcceptableOrUnknown(data['element_id']!, _elementIdMeta)); elementId.isAcceptableOrUnknown(data['element_id']!, _elementIdMeta));
@ -558,6 +566,8 @@ class $PreferencesTableTable extends PreferencesTable
requiredDuringInsert: false, requiredDuringInsert: false,
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT')); GeneratedColumn.constraintIsAlways('PRIMARY KEY AUTOINCREMENT'));
static const VerificationMeta _audioQualityMeta =
const VerificationMeta('audioQuality');
@override @override
late final GeneratedColumnWithTypeConverter<SourceQualities, String> late final GeneratedColumnWithTypeConverter<SourceQualities, String>
audioQuality = GeneratedColumn<String>( audioQuality = GeneratedColumn<String>(
@ -637,6 +647,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultConstraints: GeneratedColumn.constraintIsAlways( defaultConstraints: GeneratedColumn.constraintIsAlways(
'CHECK ("skip_non_music" IN (0, 1))'), 'CHECK ("skip_non_music" IN (0, 1))'),
defaultValue: const Constant(false)); defaultValue: const Constant(false));
static const VerificationMeta _closeBehaviorMeta =
const VerificationMeta('closeBehavior');
@override @override
late final GeneratedColumnWithTypeConverter<CloseBehavior, String> late final GeneratedColumnWithTypeConverter<CloseBehavior, String>
closeBehavior = GeneratedColumn<String>( closeBehavior = GeneratedColumn<String>(
@ -646,6 +658,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(CloseBehavior.close.name)) defaultValue: Constant(CloseBehavior.close.name))
.withConverter<CloseBehavior>( .withConverter<CloseBehavior>(
$PreferencesTableTable.$convertercloseBehavior); $PreferencesTableTable.$convertercloseBehavior);
static const VerificationMeta _accentColorSchemeMeta =
const VerificationMeta('accentColorScheme');
@override @override
late final GeneratedColumnWithTypeConverter<SpotubeColor, String> late final GeneratedColumnWithTypeConverter<SpotubeColor, String>
accentColorScheme = GeneratedColumn<String>( accentColorScheme = GeneratedColumn<String>(
@ -655,6 +669,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: const Constant("Slate:0xff64748b")) defaultValue: const Constant("Slate:0xff64748b"))
.withConverter<SpotubeColor>( .withConverter<SpotubeColor>(
$PreferencesTableTable.$converteraccentColorScheme); $PreferencesTableTable.$converteraccentColorScheme);
static const VerificationMeta _layoutModeMeta =
const VerificationMeta('layoutMode');
@override @override
late final GeneratedColumnWithTypeConverter<LayoutMode, String> layoutMode = late final GeneratedColumnWithTypeConverter<LayoutMode, String> layoutMode =
GeneratedColumn<String>('layout_mode', aliasedName, false, GeneratedColumn<String>('layout_mode', aliasedName, false,
@ -663,6 +679,7 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(LayoutMode.adaptive.name)) defaultValue: Constant(LayoutMode.adaptive.name))
.withConverter<LayoutMode>( .withConverter<LayoutMode>(
$PreferencesTableTable.$converterlayoutMode); $PreferencesTableTable.$converterlayoutMode);
static const VerificationMeta _localeMeta = const VerificationMeta('locale');
@override @override
late final GeneratedColumnWithTypeConverter<Locale, String> locale = late final GeneratedColumnWithTypeConverter<Locale, String> locale =
GeneratedColumn<String>('locale', aliasedName, false, GeneratedColumn<String>('locale', aliasedName, false,
@ -671,6 +688,7 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: const Constant( defaultValue: const Constant(
'{"languageCode":"system","countryCode":"system"}')) '{"languageCode":"system","countryCode":"system"}'))
.withConverter<Locale>($PreferencesTableTable.$converterlocale); .withConverter<Locale>($PreferencesTableTable.$converterlocale);
static const VerificationMeta _marketMeta = const VerificationMeta('market');
@override @override
late final GeneratedColumnWithTypeConverter<Market, String> market = late final GeneratedColumnWithTypeConverter<Market, String> market =
GeneratedColumn<String>('market', aliasedName, false, GeneratedColumn<String>('market', aliasedName, false,
@ -678,6 +696,8 @@ class $PreferencesTableTable extends PreferencesTable
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: Constant(Market.US.name)) defaultValue: Constant(Market.US.name))
.withConverter<Market>($PreferencesTableTable.$convertermarket); .withConverter<Market>($PreferencesTableTable.$convertermarket);
static const VerificationMeta _searchModeMeta =
const VerificationMeta('searchMode');
@override @override
late final GeneratedColumnWithTypeConverter<SearchMode, String> searchMode = late final GeneratedColumnWithTypeConverter<SearchMode, String> searchMode =
GeneratedColumn<String>('search_mode', aliasedName, false, GeneratedColumn<String>('search_mode', aliasedName, false,
@ -694,6 +714,8 @@ class $PreferencesTableTable extends PreferencesTable
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: const Constant("")); defaultValue: const Constant(""));
static const VerificationMeta _localLibraryLocationMeta =
const VerificationMeta('localLibraryLocation');
@override @override
late final GeneratedColumnWithTypeConverter<List<String>, String> late final GeneratedColumnWithTypeConverter<List<String>, String>
localLibraryLocation = GeneratedColumn<String>( localLibraryLocation = GeneratedColumn<String>(
@ -719,6 +741,8 @@ class $PreferencesTableTable extends PreferencesTable
type: DriftSqlType.string, type: DriftSqlType.string,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: const Constant("https://inv.nadeko.net")); defaultValue: const Constant("https://inv.nadeko.net"));
static const VerificationMeta _themeModeMeta =
const VerificationMeta('themeMode');
@override @override
late final GeneratedColumnWithTypeConverter<ThemeMode, String> themeMode = late final GeneratedColumnWithTypeConverter<ThemeMode, String> themeMode =
GeneratedColumn<String>('theme_mode', aliasedName, false, GeneratedColumn<String>('theme_mode', aliasedName, false,
@ -726,6 +750,8 @@ class $PreferencesTableTable extends PreferencesTable
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: Constant(ThemeMode.system.name)) defaultValue: Constant(ThemeMode.system.name))
.withConverter<ThemeMode>($PreferencesTableTable.$converterthemeMode); .withConverter<ThemeMode>($PreferencesTableTable.$converterthemeMode);
static const VerificationMeta _audioSourceMeta =
const VerificationMeta('audioSource');
@override @override
late final GeneratedColumnWithTypeConverter<AudioSource, String> audioSource = late final GeneratedColumnWithTypeConverter<AudioSource, String> audioSource =
GeneratedColumn<String>('audio_source', aliasedName, false, GeneratedColumn<String>('audio_source', aliasedName, false,
@ -734,6 +760,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(AudioSource.youtube.name)) defaultValue: Constant(AudioSource.youtube.name))
.withConverter<AudioSource>( .withConverter<AudioSource>(
$PreferencesTableTable.$converteraudioSource); $PreferencesTableTable.$converteraudioSource);
static const VerificationMeta _youtubeClientEngineMeta =
const VerificationMeta('youtubeClientEngine');
@override @override
late final GeneratedColumnWithTypeConverter<YoutubeClientEngine, String> late final GeneratedColumnWithTypeConverter<YoutubeClientEngine, String>
youtubeClientEngine = GeneratedColumn<String>( youtubeClientEngine = GeneratedColumn<String>(
@ -743,6 +771,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name)) defaultValue: Constant(YoutubeClientEngine.youtubeExplode.name))
.withConverter<YoutubeClientEngine>( .withConverter<YoutubeClientEngine>(
$PreferencesTableTable.$converteryoutubeClientEngine); $PreferencesTableTable.$converteryoutubeClientEngine);
static const VerificationMeta _streamMusicCodecMeta =
const VerificationMeta('streamMusicCodec');
@override @override
late final GeneratedColumnWithTypeConverter<SourceCodecs, String> late final GeneratedColumnWithTypeConverter<SourceCodecs, String>
streamMusicCodec = GeneratedColumn<String>( streamMusicCodec = GeneratedColumn<String>(
@ -752,6 +782,8 @@ class $PreferencesTableTable extends PreferencesTable
defaultValue: Constant(SourceCodecs.weba.name)) defaultValue: Constant(SourceCodecs.weba.name))
.withConverter<SourceCodecs>( .withConverter<SourceCodecs>(
$PreferencesTableTable.$converterstreamMusicCodec); $PreferencesTableTable.$converterstreamMusicCodec);
static const VerificationMeta _downloadMusicCodecMeta =
const VerificationMeta('downloadMusicCodec');
@override @override
late final GeneratedColumnWithTypeConverter<SourceCodecs, String> late final GeneratedColumnWithTypeConverter<SourceCodecs, String>
downloadMusicCodec = GeneratedColumn<String>( downloadMusicCodec = GeneratedColumn<String>(
@ -855,6 +887,7 @@ class $PreferencesTableTable extends PreferencesTable
if (data.containsKey('id')) { if (data.containsKey('id')) {
context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta)); context.handle(_idMeta, id.isAcceptableOrUnknown(data['id']!, _idMeta));
} }
context.handle(_audioQualityMeta, const VerificationResult.success());
if (data.containsKey('album_color_sync')) { if (data.containsKey('album_color_sync')) {
context.handle( context.handle(
_albumColorSyncMeta, _albumColorSyncMeta,
@ -897,12 +930,20 @@ class $PreferencesTableTable extends PreferencesTable
skipNonMusic.isAcceptableOrUnknown( skipNonMusic.isAcceptableOrUnknown(
data['skip_non_music']!, _skipNonMusicMeta)); data['skip_non_music']!, _skipNonMusicMeta));
} }
context.handle(_closeBehaviorMeta, const VerificationResult.success());
context.handle(_accentColorSchemeMeta, const VerificationResult.success());
context.handle(_layoutModeMeta, const VerificationResult.success());
context.handle(_localeMeta, const VerificationResult.success());
context.handle(_marketMeta, const VerificationResult.success());
context.handle(_searchModeMeta, const VerificationResult.success());
if (data.containsKey('download_location')) { if (data.containsKey('download_location')) {
context.handle( context.handle(
_downloadLocationMeta, _downloadLocationMeta,
downloadLocation.isAcceptableOrUnknown( downloadLocation.isAcceptableOrUnknown(
data['download_location']!, _downloadLocationMeta)); data['download_location']!, _downloadLocationMeta));
} }
context.handle(
_localLibraryLocationMeta, const VerificationResult.success());
if (data.containsKey('piped_instance')) { if (data.containsKey('piped_instance')) {
context.handle( context.handle(
_pipedInstanceMeta, _pipedInstanceMeta,
@ -915,6 +956,12 @@ class $PreferencesTableTable extends PreferencesTable
invidiousInstance.isAcceptableOrUnknown( invidiousInstance.isAcceptableOrUnknown(
data['invidious_instance']!, _invidiousInstanceMeta)); data['invidious_instance']!, _invidiousInstanceMeta));
} }
context.handle(_themeModeMeta, const VerificationResult.success());
context.handle(_audioSourceMeta, const VerificationResult.success());
context.handle(
_youtubeClientEngineMeta, const VerificationResult.success());
context.handle(_streamMusicCodecMeta, const VerificationResult.success());
context.handle(_downloadMusicCodecMeta, const VerificationResult.success());
if (data.containsKey('discord_presence')) { if (data.containsKey('discord_presence')) {
context.handle( context.handle(
_discordPresenceMeta, _discordPresenceMeta,
@ -1983,6 +2030,8 @@ class $ScrobblerTableTable extends ScrobblerTable
late final GeneratedColumn<String> username = GeneratedColumn<String>( late final GeneratedColumn<String> username = GeneratedColumn<String>(
'username', aliasedName, false, 'username', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _passwordHashMeta =
const VerificationMeta('passwordHash');
@override @override
late final GeneratedColumnWithTypeConverter<DecryptedText, String> late final GeneratedColumnWithTypeConverter<DecryptedText, String>
passwordHash = GeneratedColumn<String>( passwordHash = GeneratedColumn<String>(
@ -2015,6 +2064,7 @@ class $ScrobblerTableTable extends ScrobblerTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_usernameMeta); context.missing(_usernameMeta);
} }
context.handle(_passwordHashMeta, const VerificationResult.success());
return context; return context;
} }
@ -2545,6 +2595,8 @@ class $SourceMatchTableTable extends SourceMatchTable
late final GeneratedColumn<String> sourceId = GeneratedColumn<String>( late final GeneratedColumn<String> sourceId = GeneratedColumn<String>(
'source_id', aliasedName, false, 'source_id', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _sourceTypeMeta =
const VerificationMeta('sourceType');
@override @override
late final GeneratedColumnWithTypeConverter<SourceType, String> sourceType = late final GeneratedColumnWithTypeConverter<SourceType, String> sourceType =
GeneratedColumn<String>('source_type', aliasedName, false, GeneratedColumn<String>('source_type', aliasedName, false,
@ -2590,6 +2642,7 @@ class $SourceMatchTableTable extends SourceMatchTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_sourceIdMeta); context.missing(_sourceIdMeta);
} }
context.handle(_sourceTypeMeta, const VerificationResult.success());
if (data.containsKey('created_at')) { if (data.containsKey('created_at')) {
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
@ -2848,6 +2901,8 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
requiredDuringInsert: true, requiredDuringInsert: true,
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))')); GeneratedColumn.constraintIsAlways('CHECK ("playing" IN (0, 1))'));
static const VerificationMeta _loopModeMeta =
const VerificationMeta('loopMode');
@override @override
late final GeneratedColumnWithTypeConverter<PlaylistMode, String> loopMode = late final GeneratedColumnWithTypeConverter<PlaylistMode, String> loopMode =
GeneratedColumn<String>('loop_mode', aliasedName, false, GeneratedColumn<String>('loop_mode', aliasedName, false,
@ -2863,12 +2918,15 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
requiredDuringInsert: true, requiredDuringInsert: true,
defaultConstraints: defaultConstraints:
GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))')); GeneratedColumn.constraintIsAlways('CHECK ("shuffled" IN (0, 1))'));
static const VerificationMeta _collectionsMeta =
const VerificationMeta('collections');
@override @override
late final GeneratedColumnWithTypeConverter<List<String>, String> late final GeneratedColumnWithTypeConverter<List<String>, String>
collections = GeneratedColumn<String>('collections', aliasedName, false, collections = GeneratedColumn<String>('collections', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true) type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<List<String>>( .withConverter<List<String>>(
$AudioPlayerStateTableTable.$convertercollections); $AudioPlayerStateTableTable.$convertercollections);
static const VerificationMeta _tracksMeta = const VerificationMeta('tracks');
@override @override
late final GeneratedColumnWithTypeConverter<List<SpotubeTrackObject>, String> late final GeneratedColumnWithTypeConverter<List<SpotubeTrackObject>, String>
tracks = GeneratedColumn<String>('tracks', aliasedName, false, tracks = GeneratedColumn<String>('tracks', aliasedName, false,
@ -2908,12 +2966,15 @@ class $AudioPlayerStateTableTable extends AudioPlayerStateTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_playingMeta); context.missing(_playingMeta);
} }
context.handle(_loopModeMeta, const VerificationResult.success());
if (data.containsKey('shuffled')) { if (data.containsKey('shuffled')) {
context.handle(_shuffledMeta, context.handle(_shuffledMeta,
shuffled.isAcceptableOrUnknown(data['shuffled']!, _shuffledMeta)); shuffled.isAcceptableOrUnknown(data['shuffled']!, _shuffledMeta));
} else if (isInserting) { } else if (isInserting) {
context.missing(_shuffledMeta); context.missing(_shuffledMeta);
} }
context.handle(_collectionsMeta, const VerificationResult.success());
context.handle(_tracksMeta, const VerificationResult.success());
if (data.containsKey('current_index')) { if (data.containsKey('current_index')) {
context.handle( context.handle(
_currentIndexMeta, _currentIndexMeta,
@ -3244,6 +3305,7 @@ class $HistoryTableTable extends HistoryTable
type: DriftSqlType.dateTime, type: DriftSqlType.dateTime,
requiredDuringInsert: false, requiredDuringInsert: false,
defaultValue: currentDateAndTime); defaultValue: currentDateAndTime);
static const VerificationMeta _typeMeta = const VerificationMeta('type');
@override @override
late final GeneratedColumnWithTypeConverter<HistoryEntryType, String> type = late final GeneratedColumnWithTypeConverter<HistoryEntryType, String> type =
GeneratedColumn<String>('type', aliasedName, false, GeneratedColumn<String>('type', aliasedName, false,
@ -3254,6 +3316,7 @@ class $HistoryTableTable extends HistoryTable
late final GeneratedColumn<String> itemId = GeneratedColumn<String>( late final GeneratedColumn<String> itemId = GeneratedColumn<String>(
'item_id', aliasedName, false, 'item_id', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _dataMeta = const VerificationMeta('data');
@override @override
late final GeneratedColumnWithTypeConverter<Map<String, dynamic>, String> late final GeneratedColumnWithTypeConverter<Map<String, dynamic>, String>
data = GeneratedColumn<String>('data', aliasedName, false, data = GeneratedColumn<String>('data', aliasedName, false,
@ -3279,12 +3342,14 @@ class $HistoryTableTable extends HistoryTable
context.handle(_createdAtMeta, context.handle(_createdAtMeta,
createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta)); createdAt.isAcceptableOrUnknown(data['created_at']!, _createdAtMeta));
} }
context.handle(_typeMeta, const VerificationResult.success());
if (data.containsKey('item_id')) { if (data.containsKey('item_id')) {
context.handle(_itemIdMeta, context.handle(_itemIdMeta,
itemId.isAcceptableOrUnknown(data['item_id']!, _itemIdMeta)); itemId.isAcceptableOrUnknown(data['item_id']!, _itemIdMeta));
} else if (isInserting) { } else if (isInserting) {
context.missing(_itemIdMeta); context.missing(_itemIdMeta);
} }
context.handle(_dataMeta, const VerificationResult.success());
return context; return context;
} }
@ -3543,6 +3608,7 @@ class $LyricsTableTable extends LyricsTable
late final GeneratedColumn<String> trackId = GeneratedColumn<String>( late final GeneratedColumn<String> trackId = GeneratedColumn<String>(
'track_id', aliasedName, false, 'track_id', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _dataMeta = const VerificationMeta('data');
@override @override
late final GeneratedColumnWithTypeConverter<SubtitleSimple, String> data = late final GeneratedColumnWithTypeConverter<SubtitleSimple, String> data =
GeneratedColumn<String>('data', aliasedName, false, GeneratedColumn<String>('data', aliasedName, false,
@ -3569,6 +3635,7 @@ class $LyricsTableTable extends LyricsTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_trackIdMeta); context.missing(_trackIdMeta);
} }
context.handle(_dataMeta, const VerificationResult.success());
return context; return context;
} }
@ -3786,12 +3853,15 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable
late final GeneratedColumn<String> entryPoint = GeneratedColumn<String>( late final GeneratedColumn<String> entryPoint = GeneratedColumn<String>(
'entry_point', aliasedName, false, 'entry_point', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true); type: DriftSqlType.string, requiredDuringInsert: true);
static const VerificationMeta _apisMeta = const VerificationMeta('apis');
@override @override
late final GeneratedColumnWithTypeConverter<List<String>, String> apis = late final GeneratedColumnWithTypeConverter<List<String>, String> apis =
GeneratedColumn<String>('apis', aliasedName, false, GeneratedColumn<String>('apis', aliasedName, false,
type: DriftSqlType.string, requiredDuringInsert: true) type: DriftSqlType.string, requiredDuringInsert: true)
.withConverter<List<String>>( .withConverter<List<String>>(
$MetadataPluginsTableTable.$converterapis); $MetadataPluginsTableTable.$converterapis);
static const VerificationMeta _abilitiesMeta =
const VerificationMeta('abilities');
@override @override
late final GeneratedColumnWithTypeConverter<List<String>, String> abilities = late final GeneratedColumnWithTypeConverter<List<String>, String> abilities =
GeneratedColumn<String>('abilities', aliasedName, false, GeneratedColumn<String>('abilities', aliasedName, false,
@ -3884,6 +3954,8 @@ class $MetadataPluginsTableTable extends MetadataPluginsTable
} else if (isInserting) { } else if (isInserting) {
context.missing(_entryPointMeta); context.missing(_entryPointMeta);
} }
context.handle(_apisMeta, const VerificationResult.success());
context.handle(_abilitiesMeta, const VerificationResult.success());
if (data.containsKey('selected')) { if (data.containsKey('selected')) {
context.handle(_selectedMeta, context.handle(_selectedMeta,
selected.isAcceptableOrUnknown(data['selected']!, _selectedMeta)); selected.isAcceptableOrUnknown(data['selected']!, _selectedMeta));

View File

@ -12,14 +12,12 @@ enum CloseBehavior {
} }
enum AudioSource { enum AudioSource {
youtube("YouTube"), youtube,
piped("Piped"), piped,
jiosaavn("JioSaavn"), jiosaavn,
invidious("Invidious"), invidious;
dabMusic("DAB Music");
final String label; String get label => name[0].toUpperCase() + name.substring(1);
const AudioSource(this.label);
} }
enum YoutubeClientEngine { enum YoutubeClientEngine {
@ -41,6 +39,14 @@ enum YoutubeClientEngine {
} }
} }
enum MusicCodec {
m4a._("M4a (Best for downloaded music)"),
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
final String label;
const MusicCodec._(this.label);
}
enum SearchMode { enum SearchMode {
youtube._("YouTube"), youtube._("YouTube"),
youtubeMusic._("YouTube Music"); youtubeMusic._("YouTube Music");

View File

@ -3,8 +3,7 @@ part of '../database.dart';
enum SourceType { enum SourceType {
youtube._("YouTube"), youtube._("YouTube"),
youtubeMusic._("YouTube Music"), youtubeMusic._("YouTube Music"),
jiosaavn._("JioSaavn"), jiosaavn._("JioSaavn");
dabMusic._("DAB Music");
final String label; final String label;

File diff suppressed because it is too large Load Diff

View File

@ -103,7 +103,6 @@ class TrackSource with _$TrackSource {
required SourceQualities quality, required SourceQualities quality,
required SourceCodecs codec, required SourceCodecs codec,
required String bitrate, required String bitrate,
required String qualityLabel,
}) = _TrackSource; }) = _TrackSource;
factory TrackSource.fromJson(Map<String, dynamic> json) => factory TrackSource.fromJson(Map<String, dynamic> json) =>

View File

@ -28,12 +28,8 @@ mixin _$TrackSourceQuery {
String get isrc => throw _privateConstructorUsedError; String get isrc => throw _privateConstructorUsedError;
bool get explicit => throw _privateConstructorUsedError; bool get explicit => throw _privateConstructorUsedError;
/// Serializes this TrackSourceQuery to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of TrackSourceQuery
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TrackSourceQueryCopyWith<TrackSourceQuery> get copyWith => $TrackSourceQueryCopyWith<TrackSourceQuery> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -64,8 +60,6 @@ class _$TrackSourceQueryCopyWithImpl<$Res, $Val extends TrackSourceQuery>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -136,8 +130,6 @@ class __$$TrackSourceQueryImplCopyWithImpl<$Res>
$Res Function(_$TrackSourceQueryImpl) _then) $Res Function(_$TrackSourceQueryImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of TrackSourceQuery
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -241,7 +233,7 @@ class _$TrackSourceQueryImpl extends _TrackSourceQuery {
other.explicit == explicit)); other.explicit == explicit));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
@ -253,9 +245,7 @@ class _$TrackSourceQueryImpl extends _TrackSourceQuery {
isrc, isrc,
explicit); explicit);
/// Create a copy of TrackSourceQuery @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith =>
@ -298,11 +288,8 @@ abstract class _TrackSourceQuery extends TrackSourceQuery {
String get isrc; String get isrc;
@override @override
bool get explicit; bool get explicit;
/// Create a copy of TrackSourceQuery
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith => _$$TrackSourceQueryImplCopyWith<_$TrackSourceQueryImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -320,12 +307,8 @@ mixin _$TrackSourceInfo {
String get pageUrl => throw _privateConstructorUsedError; String get pageUrl => throw _privateConstructorUsedError;
int get durationMs => throw _privateConstructorUsedError; int get durationMs => throw _privateConstructorUsedError;
/// Serializes this TrackSourceInfo to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of TrackSourceInfo
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TrackSourceInfoCopyWith<TrackSourceInfo> get copyWith => $TrackSourceInfoCopyWith<TrackSourceInfo> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -355,8 +338,6 @@ class _$TrackSourceInfoCopyWithImpl<$Res, $Val extends TrackSourceInfo>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -421,8 +402,6 @@ class __$$TrackSourceInfoImplCopyWithImpl<$Res>
_$TrackSourceInfoImpl _value, $Res Function(_$TrackSourceInfoImpl) _then) _$TrackSourceInfoImpl _value, $Res Function(_$TrackSourceInfoImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of TrackSourceInfo
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -509,14 +488,12 @@ class _$TrackSourceInfoImpl implements _TrackSourceInfo {
other.durationMs == durationMs)); other.durationMs == durationMs));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, id, title, artists, thumbnail, pageUrl, durationMs); runtimeType, id, title, artists, thumbnail, pageUrl, durationMs);
/// Create a copy of TrackSourceInfo @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith =>
@ -555,11 +532,8 @@ abstract class _TrackSourceInfo implements TrackSourceInfo {
String get pageUrl; String get pageUrl;
@override @override
int get durationMs; int get durationMs;
/// Create a copy of TrackSourceInfo
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith => _$$TrackSourceInfoImplCopyWith<_$TrackSourceInfoImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -574,14 +548,9 @@ mixin _$TrackSource {
SourceQualities get quality => throw _privateConstructorUsedError; SourceQualities get quality => throw _privateConstructorUsedError;
SourceCodecs get codec => throw _privateConstructorUsedError; SourceCodecs get codec => throw _privateConstructorUsedError;
String get bitrate => throw _privateConstructorUsedError; String get bitrate => throw _privateConstructorUsedError;
String get qualityLabel => throw _privateConstructorUsedError;
/// Serializes this TrackSource to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of TrackSource
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$TrackSourceCopyWith<TrackSource> get copyWith => $TrackSourceCopyWith<TrackSource> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -596,8 +565,7 @@ abstract class $TrackSourceCopyWith<$Res> {
{String url, {String url,
SourceQualities quality, SourceQualities quality,
SourceCodecs codec, SourceCodecs codec,
String bitrate, String bitrate});
String qualityLabel});
} }
/// @nodoc /// @nodoc
@ -610,8 +578,6 @@ class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -619,7 +585,6 @@ class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource>
Object? quality = null, Object? quality = null,
Object? codec = null, Object? codec = null,
Object? bitrate = null, Object? bitrate = null,
Object? qualityLabel = null,
}) { }) {
return _then(_value.copyWith( return _then(_value.copyWith(
url: null == url url: null == url
@ -638,10 +603,6 @@ class _$TrackSourceCopyWithImpl<$Res, $Val extends TrackSource>
? _value.bitrate ? _value.bitrate
: bitrate // ignore: cast_nullable_to_non_nullable : bitrate // ignore: cast_nullable_to_non_nullable
as String, as String,
qualityLabel: null == qualityLabel
? _value.qualityLabel
: qualityLabel // ignore: cast_nullable_to_non_nullable
as String,
) as $Val); ) as $Val);
} }
} }
@ -658,8 +619,7 @@ abstract class _$$TrackSourceImplCopyWith<$Res>
{String url, {String url,
SourceQualities quality, SourceQualities quality,
SourceCodecs codec, SourceCodecs codec,
String bitrate, String bitrate});
String qualityLabel});
} }
/// @nodoc /// @nodoc
@ -670,8 +630,6 @@ class __$$TrackSourceImplCopyWithImpl<$Res>
_$TrackSourceImpl _value, $Res Function(_$TrackSourceImpl) _then) _$TrackSourceImpl _value, $Res Function(_$TrackSourceImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of TrackSource
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -679,7 +637,6 @@ class __$$TrackSourceImplCopyWithImpl<$Res>
Object? quality = null, Object? quality = null,
Object? codec = null, Object? codec = null,
Object? bitrate = null, Object? bitrate = null,
Object? qualityLabel = null,
}) { }) {
return _then(_$TrackSourceImpl( return _then(_$TrackSourceImpl(
url: null == url url: null == url
@ -698,10 +655,6 @@ class __$$TrackSourceImplCopyWithImpl<$Res>
? _value.bitrate ? _value.bitrate
: bitrate // ignore: cast_nullable_to_non_nullable : bitrate // ignore: cast_nullable_to_non_nullable
as String, as String,
qualityLabel: null == qualityLabel
? _value.qualityLabel
: qualityLabel // ignore: cast_nullable_to_non_nullable
as String,
)); ));
} }
} }
@ -713,8 +666,7 @@ class _$TrackSourceImpl implements _TrackSource {
{required this.url, {required this.url,
required this.quality, required this.quality,
required this.codec, required this.codec,
required this.bitrate, required this.bitrate});
required this.qualityLabel});
factory _$TrackSourceImpl.fromJson(Map<String, dynamic> json) => factory _$TrackSourceImpl.fromJson(Map<String, dynamic> json) =>
_$$TrackSourceImplFromJson(json); _$$TrackSourceImplFromJson(json);
@ -727,12 +679,10 @@ class _$TrackSourceImpl implements _TrackSource {
final SourceCodecs codec; final SourceCodecs codec;
@override @override
final String bitrate; final String bitrate;
@override
final String qualityLabel;
@override @override
String toString() { String toString() {
return 'TrackSource(url: $url, quality: $quality, codec: $codec, bitrate: $bitrate, qualityLabel: $qualityLabel)'; return 'TrackSource(url: $url, quality: $quality, codec: $codec, bitrate: $bitrate)';
} }
@override @override
@ -743,19 +693,14 @@ class _$TrackSourceImpl implements _TrackSource {
(identical(other.url, url) || other.url == url) && (identical(other.url, url) || other.url == url) &&
(identical(other.quality, quality) || other.quality == quality) && (identical(other.quality, quality) || other.quality == quality) &&
(identical(other.codec, codec) || other.codec == codec) && (identical(other.codec, codec) || other.codec == codec) &&
(identical(other.bitrate, bitrate) || other.bitrate == bitrate) && (identical(other.bitrate, bitrate) || other.bitrate == bitrate));
(identical(other.qualityLabel, qualityLabel) ||
other.qualityLabel == qualityLabel));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => int get hashCode => Object.hash(runtimeType, url, quality, codec, bitrate);
Object.hash(runtimeType, url, quality, codec, bitrate, qualityLabel);
/// Create a copy of TrackSource @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith => _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith =>
@ -774,8 +719,7 @@ abstract class _TrackSource implements TrackSource {
{required final String url, {required final String url,
required final SourceQualities quality, required final SourceQualities quality,
required final SourceCodecs codec, required final SourceCodecs codec,
required final String bitrate, required final String bitrate}) = _$TrackSourceImpl;
required final String qualityLabel}) = _$TrackSourceImpl;
factory _TrackSource.fromJson(Map<String, dynamic> json) = factory _TrackSource.fromJson(Map<String, dynamic> json) =
_$TrackSourceImpl.fromJson; _$TrackSourceImpl.fromJson;
@ -789,12 +733,7 @@ abstract class _TrackSource implements TrackSource {
@override @override
String get bitrate; String get bitrate;
@override @override
String get qualityLabel; @JsonKey(ignore: true)
/// 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 => _$$TrackSourceImplCopyWith<_$TrackSourceImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@ -36,7 +36,6 @@ const _$AudioSourceEnumMap = {
AudioSource.piped: 'piped', AudioSource.piped: 'piped',
AudioSource.jiosaavn: 'jiosaavn', AudioSource.jiosaavn: 'jiosaavn',
AudioSource.invidious: 'invidious', AudioSource.invidious: 'invidious',
AudioSource.dabMusic: 'dabMusic',
}; };
_$TrackSourceQueryImpl _$$TrackSourceQueryImplFromJson(Map json) => _$TrackSourceQueryImpl _$$TrackSourceQueryImplFromJson(Map json) =>
@ -89,7 +88,6 @@ _$TrackSourceImpl _$$TrackSourceImplFromJson(Map json) => _$TrackSourceImpl(
quality: $enumDecode(_$SourceQualitiesEnumMap, json['quality']), quality: $enumDecode(_$SourceQualitiesEnumMap, json['quality']),
codec: $enumDecode(_$SourceCodecsEnumMap, json['codec']), codec: $enumDecode(_$SourceCodecsEnumMap, json['codec']),
bitrate: json['bitrate'] as String, bitrate: json['bitrate'] as String,
qualityLabel: json['qualityLabel'] as String,
); );
Map<String, dynamic> _$$TrackSourceImplToJson(_$TrackSourceImpl instance) => Map<String, dynamic> _$$TrackSourceImplToJson(_$TrackSourceImpl instance) =>
@ -98,11 +96,9 @@ Map<String, dynamic> _$$TrackSourceImplToJson(_$TrackSourceImpl instance) =>
'quality': _$SourceQualitiesEnumMap[instance.quality]!, 'quality': _$SourceQualitiesEnumMap[instance.quality]!,
'codec': _$SourceCodecsEnumMap[instance.codec]!, 'codec': _$SourceCodecsEnumMap[instance.codec]!,
'bitrate': instance.bitrate, 'bitrate': instance.bitrate,
'qualityLabel': instance.qualityLabel,
}; };
const _$SourceQualitiesEnumMap = { const _$SourceQualitiesEnumMap = {
SourceQualities.uncompressed: 'uncompressed',
SourceQualities.high: 'high', SourceQualities.high: 'high',
SourceQualities.medium: 'medium', SourceQualities.medium: 'medium',
SourceQualities.low: 'low', SourceQualities.low: 'low',
@ -111,6 +107,4 @@ const _$SourceQualitiesEnumMap = {
const _$SourceCodecsEnumMap = { const _$SourceCodecsEnumMap = {
SourceCodecs.m4a: 'm4a', SourceCodecs.m4a: 'm4a',
SourceCodecs.weba: 'weba', SourceCodecs.weba: 'weba',
SourceCodecs.mp3: 'mp3',
SourceCodecs.flac: 'flac',
}; };

View File

@ -91,27 +91,10 @@ class MetadataInstalledPluginItem extends HookConsumerWidget {
) )
else ...[ else ...[
Text(context.l10n.author_name(plugin.author)), Text(context.l10n.author_name(plugin.author)),
Container( DestructiveBadge(
padding: const EdgeInsets.symmetric( leading: const Icon(SpotubeIcons.warning),
horizontal: 6, child: Text(context.l10n.third_party),
vertical: 2, )
),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 4,
children: [
const Icon(SpotubeIcons.warning, size: 14),
Text(
context.l10n.third_party,
style: const TextStyle(color: Colors.white),
).xSmall
],
),
),
], ],
SecondaryBadge( SecondaryBadge(
leading: const Icon(SpotubeIcons.connect), leading: const Icon(SpotubeIcons.connect),

View File

@ -1,4 +1,3 @@
import 'package:flutter/gestures.dart';
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
@ -9,7 +8,6 @@ import 'package:spotube/extensions/context.dart';
import 'package:spotube/models/metadata/metadata.dart'; import 'package:spotube/models/metadata/metadata.dart';
import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart'; import 'package:spotube/provider/metadata_plugin/metadata_plugin_provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:url_launcher/url_launcher_string.dart';
import 'package:change_case/change_case.dart';
class MetadataPluginRepositoryItem extends HookConsumerWidget { class MetadataPluginRepositoryItem extends HookConsumerWidget {
final MetadataPluginRepository pluginRepo; final MetadataPluginRepository pluginRepo;
@ -28,198 +26,144 @@ class MetadataPluginRepositoryItem extends HookConsumerWidget {
final isInstalling = useState(false); final isInstalling = useState(false);
return Card( return Card(
child: Column( child: Basic(
mainAxisSize: MainAxisSize.min, title: Text(
crossAxisAlignment: CrossAxisAlignment.stretch, "${pluginRepo.owner == "KRTirtho" ? "" : "${pluginRepo.owner}/"}${pluginRepo.name}"),
spacing: 8, subtitle: Column(
children: [ mainAxisSize: MainAxisSize.min,
Basic( crossAxisAlignment: CrossAxisAlignment.start,
title: Text( spacing: 8,
pluginRepo.name.startsWith("spotube-plugin") children: [
? pluginRepo.name Text(pluginRepo.description),
.replaceFirst("spotube-plugin-", "") Row(
.trim() spacing: 8,
.toCapitalCase() children: [
: pluginRepo.name.toCapitalCase(), if (pluginRepo.owner == "KRTirtho") ...[
), PrimaryBadge(
subtitle: Text(pluginRepo.description), leading: Icon(SpotubeIcons.done),
trailing: Button.primary( child: Text(context.l10n.official),
enabled: !isInstalling.value,
onPressed: () async {
try {
isInstalling.value = true;
final pluginConfig = await pluginsNotifier
.downloadAndCachePlugin(pluginRepo.repoUrl);
if (!context.mounted) return;
final isOfficialPlugin = pluginRepo.owner == "KRTirtho";
final isAllowed = isOfficialPlugin
? true
: await showDialog<bool>(
context: context,
builder: (context) {
final pluginAbilities = pluginConfig.apis
.map((e) =>
context.l10n.can_access_name_api(e.name))
.join("\n\n");
return AlertDialog(
title: Text(
context.l10n.do_you_want_to_install_this_plugin,
),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.third_party_plugin_warning),
const Gap(8),
FutureBuilder(
future: pluginsNotifier
.getLogoPath(pluginConfig),
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(pluginConfig.name),
subtitle:
Text(pluginConfig.description),
);
},
),
const Gap(8),
AppMarkdown(
data:
"**${context.l10n.author}**: ${pluginConfig.author}\n\n"
"**${context.l10n.repository}**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n"
"${context.l10n.this_plugin_can_do_following}:\n\n"
"$pluginAbilities",
),
],
),
actions: [
Button.secondary(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(context.l10n.decline),
),
Button.primary(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(context.l10n.accept),
),
],
);
},
);
if (isAllowed != true) return;
await pluginsNotifier.addPlugin(pluginConfig);
} finally {
if (context.mounted) {
isInstalling.value = false;
}
}
},
leading: isInstalling.value
? SizedBox.square(
dimension: 20,
child: CircularProgressIndicator(
color: context.theme.colorScheme.primaryForeground,
),
)
: const Icon(SpotubeIcons.add),
child: Text(context.l10n.install),
),
),
if (pluginRepo.owner != "KRTirtho")
Text.rich(
TextSpan(
children: [
TextSpan(text: context.l10n.source),
TextSpan(
text: pluginRepo.repoUrl.replaceAll("https://", ""),
style: const TextStyle(
color: Colors.blue,
decoration: TextDecoration.underline,
),
recognizer: TapGestureRecognizer()
..onTap = () async {
launchUrlString(pluginRepo.repoUrl);
},
), ),
], SecondaryBadge(
), leading: host == "github.com"
style: context.theme.typography.xSmall, ? const Icon(SpotubeIcons.github)
), : null,
Wrap( child: Text(host),
spacing: 8, onPressed: () {
runSpacing: 8, launchUrlString(pluginRepo.repoUrl);
children: [ },
if (pluginRepo.owner == "KRTirtho")
PrimaryBadge(
leading: const Icon(SpotubeIcons.done),
child: Text(context.l10n.official),
)
else ...[
Text(
context.l10n.author_name(pluginRepo.owner),
style: context.theme.typography.xSmall,
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 6,
vertical: 2,
), ),
decoration: BoxDecoration( ] else ...[
color: Colors.blue, Text(context.l10n.author_name(pluginRepo.owner)),
borderRadius: BorderRadius.circular(6), DestructiveBadge(
), leading: const Icon(SpotubeIcons.warning),
child: Row( child: Text(context.l10n.third_party),
mainAxisSize: MainAxisSize.min, )
spacing: 4, ]
children: [
const Icon(SpotubeIcons.warning, size: 14),
Text(
context.l10n.third_party,
style: const TextStyle(color: Colors.white),
).xSmall
],
),
),
], ],
SecondaryBadge( ),
leading: host == "github.com" ],
? const Icon(SpotubeIcons.github) ),
: null, trailing: Button.primary(
child: Text(host), enabled: !isInstalling.value,
onPressed: () { onPressed: () async {
launchUrlString(pluginRepo.repoUrl); try {
}, isInstalling.value = true;
), final pluginConfig = await pluginsNotifier
], .downloadAndCachePlugin(pluginRepo.repoUrl);
),
], if (!context.mounted) return;
final isOfficialPlugin = pluginRepo.owner == "KRTirtho";
final isAllowed = isOfficialPlugin
? true
: await showDialog<bool>(
context: context,
builder: (context) {
final pluginAbilities = pluginConfig.apis
.map(
(e) => context.l10n.can_access_name_api(e.name))
.join("\n\n");
return AlertDialog(
title: Text(
context.l10n.do_you_want_to_install_this_plugin),
content: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(context.l10n.third_party_plugin_warning),
const Gap(8),
FutureBuilder(
future:
pluginsNotifier.getLogoPath(pluginConfig),
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(pluginConfig.name),
subtitle: Text(pluginConfig.description),
);
},
),
const Gap(8),
AppMarkdown(
data:
"**${context.l10n.author}**: ${pluginConfig.author}\n\n"
"**${context.l10n.repository}**: [${pluginConfig.repository ?? 'N/A'}](${pluginConfig.repository})\n\n\n\n"
"${context.l10n.this_plugin_can_do_following}:\n\n"
"$pluginAbilities",
),
],
),
actions: [
Button.secondary(
onPressed: () {
Navigator.of(context).pop(false);
},
child: Text(context.l10n.decline),
),
Button.primary(
onPressed: () {
Navigator.of(context).pop(true);
},
child: Text(context.l10n.accept),
),
],
);
},
);
if (isAllowed != true) return;
await pluginsNotifier.addPlugin(pluginConfig);
} finally {
if (context.mounted) {
isInstalling.value = false;
}
}
},
leading: isInstalling.value
? const CircularProgressIndicator()
: const Icon(SpotubeIcons.add),
child: Text(context.l10n.install),
),
), ),
); );
} }

View File

@ -46,14 +46,6 @@ class PlayerView extends HookConsumerWidget {
final isLocalTrack = currentActiveTrack is SpotubeLocalTrackObject; final isLocalTrack = currentActiveTrack is SpotubeLocalTrackObject;
final mediaQuery = MediaQuery.sizeOf(context); final mediaQuery = MediaQuery.sizeOf(context);
final activeSourceCodec = useMemoized(
() {
return currentActiveTrackSource
?.getSourceOfCodec(currentActiveTrackSource.codec);
},
[currentActiveTrackSource?.sources, currentActiveTrackSource?.codec],
);
final shouldHide = useState(true); final shouldHide = useState(true);
ref.listen(navigationPanelHeight, (_, height) { ref.listen(navigationPanelHeight, (_, height) {
@ -275,21 +267,6 @@ class PlayerView extends HookConsumerWidget {
); );
}), }),
), ),
const Gap(25),
if (activeSourceCodec != null)
OutlineBadge(
style: const ButtonStyle.outline(
size: ButtonSize.normal,
density: ButtonDensity.dense,
shape: ButtonShape.rectangle,
).copyWith(
textStyle: (context, states, value) {
return value.copyWith(fontWeight: FontWeight.w500);
},
),
leading: const Icon(SpotubeIcons.lightningOutlined),
child: Text(activeSourceCodec.qualityLabel),
)
], ],
), ),
), ),

View File

@ -1,4 +1,3 @@
import 'package:flutter/material.dart' show Badge;
import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart';
@ -8,6 +7,7 @@ import 'package:spotube/components/ui/button_tile.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
import 'package:spotube/modules/getting_started/blur_card.dart'; import 'package:spotube/modules/getting_started/blur_card.dart';
import 'package:spotube/extensions/context.dart'; import 'package:spotube/extensions/context.dart';
import 'package:spotube/extensions/string.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.dart'; import 'package:spotube/provider/user_preferences/user_preferences_provider.dart';
final audioSourceToIconMap = { final audioSourceToIconMap = {
@ -23,8 +23,6 @@ final audioSourceToIconMap = {
), ),
AudioSource.jiosaavn: AudioSource.jiosaavn:
Assets.images.logos.jiosaavn.image(width: 20, height: 20), Assets.images.logos.jiosaavn.image(width: 20, height: 20),
AudioSource.dabMusic:
Assets.images.logos.dabMusic.image(width: 20, height: 20),
}; };
class GettingStartedPagePlaybackSection extends HookConsumerWidget { class GettingStartedPagePlaybackSection extends HookConsumerWidget {
@ -49,10 +47,8 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
AudioSource.piped: context.l10n.piped_source_description, AudioSource.piped: context.l10n.piped_source_description,
AudioSource.jiosaavn: AudioSource.jiosaavn:
"${context.l10n.jiosaavn_source_description}\n" "${context.l10n.jiosaavn_source_description}\n"
"${context.l10n.highest_quality("320kbps mp4")}", "${context.l10n.highest_quality("320kbps mp")}",
AudioSource.invidious: context.l10n.invidious_source_description, AudioSource.invidious: context.l10n.invidious_source_description,
AudioSource.dabMusic: "${context.l10n.dab_music_source_description}\n"
"${context.l10n.highest_quality("320kbps mp3, HI-RES 24bit 44.1kHz-96kHz flac")}",
}, },
[]); []);
@ -74,34 +70,43 @@ class GettingStartedPagePlaybackSection extends HookConsumerWidget {
child: Text(context.l10n.select_audio_source).semiBold().large(), child: Text(context.l10n.select_audio_source).semiBold().large(),
), ),
const Gap(16), const Gap(16),
RadioGroup<AudioSource>( Select<AudioSource>(
value: preferences.audioSource, value: preferences.audioSource,
onChanged: (value) { onChanged: (value) {
if (value == null) return;
preferencesNotifier.setAudioSource(value); preferencesNotifier.setAudioSource(value);
}, },
child: Wrap( placeholder: Text(preferences.audioSource.name.capitalize()),
itemBuilder: (context, value) => Row(
mainAxisSize: MainAxisSize.min,
spacing: 6, spacing: 6,
runSpacing: 6,
children: [ children: [
for (final source in AudioSource.values) audioSourceToIconMap[value]!,
Badge( Text(value.name.capitalize()),
isLabelVisible: source == AudioSource.dabMusic,
label: const Text("NEW"),
backgroundColor: Colors.lime[300],
textColor: Colors.black,
child: RadioCard(
value: source,
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
audioSourceToIconMap[source]!,
Text(source.label),
],
),
),
),
], ],
), ),
popup: (context) {
return SelectPopup(
items: SelectItemBuilder(
childCount: AudioSource.values.length,
builder: (context, index) {
final source = AudioSource.values[index];
return SelectItemButton(
value: source,
child: Row(
mainAxisSize: MainAxisSize.min,
spacing: 6,
children: [
audioSourceToIconMap[source]!,
Text(source.name.capitalize()),
],
),
);
},
),
);
},
), ),
const Gap(16), const Gap(16),
Text( Text(

View File

@ -8,7 +8,7 @@ import 'package:flutter/material.dart' show ListTile;
import 'package:google_fonts/google_fonts.dart'; import 'package:google_fonts/google_fonts.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:piped_client/piped_client.dart'; import 'package:piped_client/piped_client.dart';
import 'package:shadcn_flutter/shadcn_flutter.dart'; import 'package:shadcn_flutter/shadcn_flutter.dart' hide Consumer;
import 'package:spotube/collections/routes.gr.dart'; import 'package:spotube/collections/routes.gr.dart';
import 'package:spotube/collections/spotube_icons.dart'; import 'package:spotube/collections/spotube_icons.dart';
import 'package:spotube/models/database/database.dart'; import 'package:spotube/models/database/database.dart';
@ -44,25 +44,18 @@ class SettingsPlaybackSection extends HookConsumerWidget {
title: Text(context.l10n.audio_quality), title: Text(context.l10n.audio_quality),
value: preferences.audioQuality, value: preferences.audioQuality,
options: [ options: [
if (preferences.audioSource == AudioSource.dabMusic)
SelectItemButton(
value: SourceQualities.uncompressed,
child: Text(context.l10n.uncompressed),
),
SelectItemButton( SelectItemButton(
value: SourceQualities.high, value: SourceQualities.high,
child: Text(context.l10n.high), child: Text(context.l10n.high),
), ),
if (preferences.audioSource != AudioSource.dabMusic) ...[ SelectItemButton(
SelectItemButton( value: SourceQualities.medium,
value: SourceQualities.medium, child: Text(context.l10n.medium),
child: Text(context.l10n.medium), ),
), SelectItemButton(
SelectItemButton( value: SourceQualities.low,
value: SourceQualities.low, child: Text(context.l10n.low),
child: Text(context.l10n.low), ),
),
]
], ],
onChanged: (value) { onChanged: (value) {
if (value != null) { if (value != null) {
@ -403,9 +396,7 @@ class SettingsPlaybackSection extends HookConsumerWidget {
onChanged: preferencesNotifier.setNormalizeAudio, onChanged: preferencesNotifier.setNormalizeAudio,
), ),
), ),
if (const [AudioSource.jiosaavn, AudioSource.dabMusic] if (preferences.audioSource != AudioSource.jiosaavn) ...[
.contains(preferences.audioSource) ==
false) ...[
AdaptiveSelectTile<SourceCodecs>( AdaptiveSelectTile<SourceCodecs>(
popupConstraints: const BoxConstraints(maxWidth: 300), popupConstraints: const BoxConstraints(maxWidth: 300),
secondary: const Icon(SpotubeIcons.stream), secondary: const Icon(SpotubeIcons.stream),

View File

@ -259,14 +259,11 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
return addTracks(tracks); return addTracks(tracks);
} }
final addableTracks = _blacklist final addableTracks = _blacklist.filter(tracks).where(
.filter(tracks)
.where(
(track) => (track) =>
allowDuplicates || allowDuplicates ||
!state.tracks.any((element) => _compareTracks(element, track)), !state.tracks.any((element) => _compareTracks(element, track)),
) );
.toList();
state = state.copyWith( state = state.copyWith(
tracks: [...addableTracks, ...state.tracks], tracks: [...addableTracks, ...state.tracks],
@ -374,12 +371,13 @@ class AudioPlayerNotifier extends Notifier<AudioPlayerState> {
} }
bool _compareTracks(SpotubeTrackObject a, SpotubeTrackObject b) { bool _compareTracks(SpotubeTrackObject a, SpotubeTrackObject b) {
if (a.runtimeType != b.runtimeType) { if ((a is SpotubeLocalTrackObject && b is! SpotubeLocalTrackObject) ||
(a is! SpotubeLocalTrackObject && b is SpotubeLocalTrackObject)) {
return false; return false;
} }
return a is SpotubeLocalTrackObject && b is SpotubeLocalTrackObject return a is SpotubeLocalTrackObject && b is SpotubeLocalTrackObject
? a.path == b.path ? (a).path == (b).path
: a.id == b.id; : a.id == b.id;
} }

View File

@ -27,12 +27,8 @@ mixin _$AudioPlayerState {
int get currentIndex => throw _privateConstructorUsedError; int get currentIndex => throw _privateConstructorUsedError;
List<SpotubeTrackObject> get tracks => throw _privateConstructorUsedError; List<SpotubeTrackObject> get tracks => throw _privateConstructorUsedError;
/// Serializes this AudioPlayerState to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// Create a copy of AudioPlayerState
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
$AudioPlayerStateCopyWith<AudioPlayerState> get copyWith => $AudioPlayerStateCopyWith<AudioPlayerState> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -62,8 +58,6 @@ class _$AudioPlayerStateCopyWithImpl<$Res, $Val extends AudioPlayerState>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -128,8 +122,6 @@ class __$$AudioPlayerStateImplCopyWithImpl<$Res>
$Res Function(_$AudioPlayerStateImpl) _then) $Res Function(_$AudioPlayerStateImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of AudioPlayerState
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -234,7 +226,7 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState {
const DeepCollectionEquality().equals(other._tracks, _tracks)); const DeepCollectionEquality().equals(other._tracks, _tracks));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash( int get hashCode => Object.hash(
runtimeType, runtimeType,
@ -245,9 +237,7 @@ class _$AudioPlayerStateImpl extends _AudioPlayerState {
currentIndex, currentIndex,
const DeepCollectionEquality().hash(_tracks)); const DeepCollectionEquality().hash(_tracks));
/// Create a copy of AudioPlayerState @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith =>
@ -287,11 +277,8 @@ abstract class _AudioPlayerState extends AudioPlayerState {
int get currentIndex; int get currentIndex;
@override @override
List<SpotubeTrackObject> get tracks; List<SpotubeTrackObject> get tracks;
/// Create a copy of AudioPlayerState
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith => _$$AudioPlayerStateImplCopyWith<_$AudioPlayerStateImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@ -214,7 +214,7 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
/// Root directory where all metadata plugins are stored. /// Root directory where all metadata plugins are stored.
Future<Directory> _getPluginRootDir() async => Directory( Future<Directory> _getPluginRootDir() async => Directory(
join( join(
(await getApplicationSupportDirectory()).path, (await getApplicationCacheDirectory()).path,
"metadata-plugins", "metadata-plugins",
), ),
); );
@ -350,8 +350,6 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
abilities: plugin.abilities.map((e) => e.name).toList(), abilities: plugin.abilities.map((e) => e.name).toList(),
pluginApiVersion: Value(plugin.pluginApiVersion), pluginApiVersion: Value(plugin.pluginApiVersion),
repository: Value(plugin.repository), repository: Value(plugin.repository),
// Setting the very first plugin as the default plugin
selected: Value(state.valueOrNull?.plugins.isEmpty ?? true),
), ),
); );
} }
@ -364,17 +362,6 @@ class MetadataPluginNotifier extends AsyncNotifier<MetadataPluginState> {
} }
await database.metadataPluginsTable.deleteWhere((tbl) => await database.metadataPluginsTable.deleteWhere((tbl) =>
tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author)); tbl.name.equals(plugin.name) & tbl.author.equals(plugin.author));
// Same here, if the removed plugin is the default plugin
// set the first available plugin as the default plugin
// only when there is 1 remaining plugin
if (state.valueOrNull?.defaultPluginConfig == plugin) {
final remainingPlugins =
state.valueOrNull?.plugins.where((p) => p != plugin) ?? [];
if (remainingPlugins.length == 1) {
await setDefaultPlugin(remainingPlugins.first);
}
}
} }
Future<void> updatePlugin( Future<void> updatePlugin(

View File

@ -12,7 +12,6 @@ final serverRouterProvider = Provider((ref) {
router.get("/ping", (Request request) => Response.ok("pong")); router.get("/ping", (Request request) => Response.ok("pong"));
router.head("/stream/<trackId>", playbackRoutes.headStreamTrackId);
router.get("/stream/<trackId>", playbackRoutes.getStreamTrackId); router.get("/stream/<trackId>", playbackRoutes.getStreamTrackId);
router.get("/playback/toggle-playback", playbackRoutes.togglePlayback); router.get("/playback/toggle-playback", playbackRoutes.togglePlayback);

View File

@ -46,95 +46,21 @@ class ServerPlaybackRoutes {
ServerPlaybackRoutes(this.ref) : dio = Dio(); ServerPlaybackRoutes(this.ref) : dio = Dio();
Future<String> _getTrackCacheFilePath(SourcedTrack track) async {
return join(
await UserPreferencesNotifier.getMusicCacheDir(),
ServiceUtils.sanitizeFilename(
'${track.query.title} - ${track.query.artists.join(",")} (${track.info.id}).${track.codec.name}',
),
);
}
Future<SourcedTrack?> _getSourcedTrack(
Request request, String trackId) async {
final track =
playlist.tracks.firstWhere((element) => element.id == trackId);
final activeSourcedTrack =
await ref.read(activeTrackSourcesProvider.future);
final sourcedTrack = activeSourcedTrack?.track.id == track.id
? activeSourcedTrack?.source
: await ref.read(
trackSourcesProvider(
//! Use [Request.requestedUri] as it contains full https url.
//! [Request.url] will exclude and starts relatively. (streams/<trackId>... basically)
TrackSourceQuery.parseUri(request.requestedUri.toString()),
).future,
);
return sourcedTrack;
}
Future<dio_lib.Response> streamTrackInformation(
Request request,
SourcedTrack track,
) async {
AppLogger.log.i(
"HEAD request for track: ${track.query.title}\n"
"Headers: ${request.headers}",
);
final trackCacheFile = File(await _getTrackCacheFilePath(track));
if (await trackCacheFile.exists() && userPreferences.cacheMusic) {
final fileLength = await trackCacheFile.length();
return dio_lib.Response(
statusCode: 200,
headers: Headers.fromMap({
"content-type": ["audio/${track.codec.name}"],
"content-length": ["$fileLength"],
"accept-ranges": ["bytes"],
"content-range": ["bytes 0-$fileLength/$fileLength"],
}),
requestOptions: RequestOptions(path: request.requestedUri.toString()),
);
}
String url = track.url ??
await ref
.read(trackSourcesProvider(track.query).notifier)
.swapWithNextSibling()
.then((track) => track.url!);
final options = Options(
headers: {
"user-agent": _randomUserAgent,
"Cache-Control": "max-age=3600",
"Connection": "keep-alive",
"host": Uri.parse(url).host,
},
validateStatus: (status) => status! < 400,
);
final res = await dio.head(url, options: options);
return res;
}
Future<({dio_lib.Response<Uint8List> response, Uint8List? bytes})> Future<({dio_lib.Response<Uint8List> response, Uint8List? bytes})>
streamTrack( streamTrack(
Request request, Request request,
SourcedTrack track, SourcedTrack track,
Map<String, dynamic> headers, Map<String, dynamic> headers,
) async { ) async {
AppLogger.log.i( final trackCacheFile = File(
"GET request for track: ${track.query.title}\n" join(
"Headers: ${request.headers}", await UserPreferencesNotifier.getMusicCacheDir(),
ServiceUtils.sanitizeFilename(
'${track.query.title} - ${track.query.artists.join(",")} (${track.info.id}).${track.codec.name}',
),
),
); );
final trackCacheFile = File(await _getTrackCacheFilePath(track));
if (await trackCacheFile.exists() && userPreferences.cacheMusic) { if (await trackCacheFile.exists() && userPreferences.cacheMusic) {
final bytes = await trackCacheFile.readAsBytes(); final bytes = await trackCacheFile.readAsBytes();
final cachedFileLength = bytes.length; final cachedFileLength = bytes.length;
@ -175,7 +101,10 @@ class ServerPlaybackRoutes {
); );
final contentLengthRes = await Future<dio_lib.Response?>.value( final contentLengthRes = await Future<dio_lib.Response?>.value(
dio.head(url, options: options), dio.head(
url,
options: options,
),
).catchError((e, stack) async { ).catchError((e, stack) async {
AppLogger.reportError(e, stack); AppLogger.reportError(e, stack);
@ -206,33 +135,25 @@ class ServerPlaybackRoutes {
); );
} }
if (headers["range"] == "bytes=0-" && track.codec == SourceCodecs.flac) { final contentLength = contentLengthRes?.headers.value("content-length");
final bufferSize =
userPreferences.audioQuality == SourceQualities.uncompressed
? 6 * 1024 * 1024 // 6MB for lossless
: 4 * 1024 * 1024; // 4MB for lossy
final endRange = min(
bufferSize,
int.parse(contentLengthRes?.headers.value("content-length") ?? "0"),
);
/// Forcing partial content range as mpv sometimes greedily wants
/// everything at one go. Slows down overall streaming.
final range = RangeHeader.parse(headers["range"] ?? "");
final contentPartialLength = int.tryParse(contentLength ?? "");
if ((range.end == null) &&
contentPartialLength != null &&
range.start == 0) {
options = options.copyWith( options = options.copyWith(
headers: { headers: {
...?options.headers, ...?options.headers,
"range": "bytes=0-$endRange", "range": "$range${(contentPartialLength * 0.3).ceil()}",
}, },
); );
} }
final res = await dio.get<Uint8List>(url, options: options); final res = await dio.get<Uint8List>(url, options: options);
AppLogger.log.i(
"Response for track: ${track.query.title}\n"
"Status Code: ${res.statusCode}\n"
"Headers: ${res.headers.map}",
);
final bytes = res.data; final bytes = res.data;
if (bytes == null || !userPreferences.cacheMusic) { if (bytes == null || !userPreferences.cacheMusic) {
@ -292,42 +213,27 @@ class ServerPlaybackRoutes {
return (bytes: bytes, response: res); return (bytes: bytes, response: res);
} }
/// @head('/stream/<trackId>')
Future<Response> headStreamTrackId(Request request, String trackId) async {
try {
final sourcedTrack = await _getSourcedTrack(request, trackId);
if (sourcedTrack == null) {
return Response.notFound("Track not found in the current queue");
}
final res = await streamTrackInformation(
request,
sourcedTrack,
);
return Response(
res.statusCode!,
headers: res.headers.map,
);
} catch (e, stack) {
AppLogger.reportError(e, stack);
return Response.internalServerError();
}
}
/// @get('/stream/<trackId>') /// @get('/stream/<trackId>')
Future<Response> getStreamTrackId(Request request, String trackId) async { Future<Response> getStreamTrackId(Request request, String trackId) async {
try { try {
final sourcedTrack = await _getSourcedTrack(request, trackId); final track =
playlist.tracks.firstWhere((element) => element.id == trackId);
if (sourcedTrack == null) { final activeSourcedTrack =
return Response.notFound("Track not found in the current queue"); await ref.read(activeTrackSourcesProvider.future);
} final sourcedTrack = activeSourcedTrack?.track.id == track.id
? activeSourcedTrack?.source
: await ref.read(
trackSourcesProvider(
//! Use [Request.requestedUri] as it contains full https url.
//! [Request.url] will exclude and starts relatively. (streams/<trackId>... basically)
TrackSourceQuery.parseUri(request.requestedUri.toString()),
).future,
);
final (bytes: audioBytes, response: res) = await streamTrack( final (bytes: audioBytes, response: res) = await streamTrack(
request, request,
sourcedTrack, sourcedTrack!,
request.headers, request.headers,
); );

View File

@ -166,7 +166,7 @@ class TrackOptionsActions {
} }
break; break;
case TrackOptionValue.playNext: case TrackOptionValue.playNext:
await playback.addTracksAtFirst([track]); playback.addTracksAtFirst([track]);
if (context.mounted) { if (context.mounted) {
showToast( showToast(

View File

@ -54,7 +54,6 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
} }
await audioPlayer.setAudioNormalization(state.normalizeAudio); await audioPlayer.setAudioNormalization(state.normalizeAudio);
await _updatePlayerBufferSize(event.audioQuality, state.audioQuality);
} catch (e, stack) { } catch (e, stack) {
AppLogger.reportError(e, stack); AppLogger.reportError(e, stack);
} }
@ -80,24 +79,6 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
}); });
} }
/// Sets audio player's buffer size based on the selected audio quality
/// Uncompressed quality gets a larger buffer size for smoother playback
/// while other qualities use a standard buffer size.
Future<void> _updatePlayerBufferSize(
SourceQualities newQuality,
SourceQualities oldQuality,
) async {
if (newQuality == SourceQualities.uncompressed) {
audioPlayer.setDemuxerBufferSize(6 * 1024 * 1024); // 6MB
return;
}
if (oldQuality == SourceQualities.uncompressed &&
newQuality != SourceQualities.uncompressed) {
audioPlayer.setDemuxerBufferSize(4 * 1024 * 1024); // 4MB
}
}
Future<void> setData(PreferencesTableCompanion data) async { Future<void> setData(PreferencesTableCompanion data) async {
final db = ref.read(databaseProvider); final db = ref.read(databaseProvider);
@ -174,7 +155,6 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
void setAudioQuality(SourceQualities quality) { void setAudioQuality(SourceQualities quality) {
setData(PreferencesTableCompanion(audioQuality: Value(quality))); setData(PreferencesTableCompanion(audioQuality: Value(quality)));
_updatePlayerBufferSize(quality, state.audioQuality);
} }
void setDownloadLocation(String downloadDir) { void setDownloadLocation(String downloadDir) {
@ -224,23 +204,6 @@ class UserPreferencesNotifier extends Notifier<PreferencesTableData> {
} }
void setAudioSource(AudioSource type) { void setAudioSource(AudioSource type) {
switch ((type, state.audioQuality)) {
// DAB music only supports high quality/uncompressed streams
case (
AudioSource.dabMusic,
SourceQualities.low || SourceQualities.medium
):
setAudioQuality(SourceQualities.high);
break;
// If the user switches from DAB music to other sources and has
// uncompressed quality selected, downgrade to high quality
case (!= AudioSource.dabMusic, SourceQualities.uncompressed):
setAudioQuality(SourceQualities.high);
break;
default:
break;
}
setData(PreferencesTableCompanion(audioSource: Value(type))); setData(PreferencesTableCompanion(audioSource: Value(type)));
} }

View File

@ -56,8 +56,6 @@ abstract class AudioPlayerInterface {
configuration: const mk.PlayerConfiguration( configuration: const mk.PlayerConfiguration(
title: "Spotube", title: "Spotube",
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error, logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
bufferSize: 4 * 1024 * 1024, // 4MB buffer
async: true,
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {

View File

@ -131,8 +131,4 @@ class SpotubeAudioPlayer extends AudioPlayerInterface
Future<void> setAudioNormalization(bool normalize) async { Future<void> setAudioNormalization(bool normalize) async {
await _mkPlayer.setAudioNormalization(normalize); await _mkPlayer.setAudioNormalization(normalize);
} }
Future<void> setDemuxerBufferSize(int sizeInBytes) async {
await _mkPlayer.setDemuxerBufferSize(sizeInBytes);
}
} }

View File

@ -121,23 +121,9 @@ class CustomPlayer extends Player {
NativePlayer get nativePlayer => platform as NativePlayer; NativePlayer get nativePlayer => platform as NativePlayer;
Future<void> insert(int index, Media media) async { Future<void> insert(int index, Media media) async {
final addedMediaCompleter = Completer<int>(); await add(media);
final playlistStream = stream.playlist.listen( await Future.delayed(const Duration(milliseconds: 100));
(event) { await move(state.playlist.medias.length - 1, index);
final mediaAddedIndex =
event.medias.indexWhere((m) => m.uri == media.uri);
if (mediaAddedIndex != -1 && !addedMediaCompleter.isCompleted) {
addedMediaCompleter.complete(mediaAddedIndex);
}
},
);
try {
await add(media);
final mediaAddedIndex = await addedMediaCompleter.future;
await move(mediaAddedIndex, index);
} finally {
playlistStream.cancel();
}
} }
Future<void> setAudioNormalization(bool normalize) async { Future<void> setAudioNormalization(bool normalize) async {
@ -147,12 +133,4 @@ class CustomPlayer extends Player {
await nativePlayer.setProperty('af', ''); await nativePlayer.setProperty('af', '');
} }
} }
Future<void> setDemuxerBufferSize(int sizeInBytes) async {
await nativePlayer.setProperty('demuxer-max-bytes', sizeInBytes.toString());
await nativePlayer.setProperty(
'demuxer-max-back-bytes',
sizeInBytes.toString(),
);
}
} }

View File

@ -30,12 +30,8 @@ mixin _$SongLink {
String? get nativeAppUriMobile => throw _privateConstructorUsedError; String? get nativeAppUriMobile => throw _privateConstructorUsedError;
String? get nativeAppUriDesktop => throw _privateConstructorUsedError; String? get nativeAppUriDesktop => throw _privateConstructorUsedError;
/// Serializes this SongLink to a JSON map.
Map<String, dynamic> toJson() => throw _privateConstructorUsedError; Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
/// 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 => $SongLinkCopyWith<SongLink> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }
@ -67,8 +63,6 @@ class _$SongLinkCopyWithImpl<$Res, $Val extends SongLink>
// ignore: unused_field // ignore: unused_field
final $Res Function($Val) _then; 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') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -151,8 +145,6 @@ class __$$SongLinkImplCopyWithImpl<$Res>
_$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then) _$SongLinkImpl _value, $Res Function(_$SongLinkImpl) _then)
: super(_value, _then); : super(_value, _then);
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
@override @override
$Res call({ $Res call({
@ -269,14 +261,12 @@ class _$SongLinkImpl implements _SongLink {
other.nativeAppUriDesktop == nativeAppUriDesktop)); other.nativeAppUriDesktop == nativeAppUriDesktop));
} }
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
@override @override
int get hashCode => Object.hash(runtimeType, displayName, linkId, platform, int get hashCode => Object.hash(runtimeType, displayName, linkId, platform,
show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop); show, uniqueId, country, url, nativeAppUriMobile, nativeAppUriDesktop);
/// Create a copy of SongLink @JsonKey(ignore: true)
/// with the given fields replaced by the non-null parameter values.
@JsonKey(includeFromJson: false, includeToJson: false)
@override @override
@pragma('vm:prefer-inline') @pragma('vm:prefer-inline')
_$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
@ -323,11 +313,8 @@ abstract class _SongLink implements SongLink {
String? get nativeAppUriMobile; String? get nativeAppUriMobile;
@override @override
String? get nativeAppUriDesktop; String? get nativeAppUriDesktop;
/// Create a copy of SongLink
/// with the given fields replaced by the non-null parameter values.
@override @override
@JsonKey(includeFromJson: false, includeToJson: false) @JsonKey(ignore: true)
_$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith => _$$SongLinkImplCopyWith<_$SongLinkImpl> get copyWith =>
throw _privateConstructorUsedError; throw _privateConstructorUsedError;
} }

View File

@ -2,16 +2,13 @@ import 'package:spotube/models/playback/track_sources.dart';
enum SourceCodecs { enum SourceCodecs {
m4a._("M4a (Best for downloaded music)"), m4a._("M4a (Best for downloaded music)"),
weba._("WebA (Best for streamed music)\nDoesn't support audio metadata"), weba._("WebA (Best for streamed music)\nDoesn't support audio metadata");
mp3._("MP3 (Widely supported audio format)"),
flac._("FLAC (Lossless, best quality)\nLarge file size");
final String label; final String label;
const SourceCodecs._(this.label); const SourceCodecs._(this.label);
} }
enum SourceQualities { enum SourceQualities {
uncompressed(3),
high(2), high(2),
medium(1), medium(1),
low(0); low(0);

View File

@ -5,7 +5,6 @@ import 'package:spotube/models/playback/track_sources.dart';
import 'package:spotube/provider/user_preferences/user_preferences_provider.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/enums.dart';
import 'package:spotube/services/sourced_track/sources/dab_music.dart';
import 'package:spotube/services/sourced_track/sources/invidious.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/jiosaavn.dart';
import 'package:spotube/services/sourced_track/sources/piped.dart'; import 'package:spotube/services/sourced_track/sources/piped.dart';
@ -75,14 +74,6 @@ abstract class SourcedTrack extends BasicSourcedTrack {
query: query, query: query,
sources: sources, sources: sources,
), ),
AudioSource.dabMusic => DABMusicSourcedTrack(
ref: ref,
source: source,
siblings: siblings,
info: info,
query: query,
sources: sources,
),
}; };
} }
@ -113,8 +104,6 @@ abstract class SourcedTrack extends BasicSourcedTrack {
await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref), await InvidiousSourcedTrack.fetchFromTrack(query: query, ref: ref),
AudioSource.jiosaavn => AudioSource.jiosaavn =>
await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref), await JioSaavnSourcedTrack.fetchFromTrack(query: query, ref: ref),
AudioSource.dabMusic =>
await DABMusicSourcedTrack.fetchFromTrack(query: query, ref: ref),
}; };
} catch (e) { } catch (e) {
if (preferences.audioSource == AudioSource.youtube) { if (preferences.audioSource == AudioSource.youtube) {
@ -140,8 +129,6 @@ abstract class SourcedTrack extends BasicSourcedTrack {
JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref), JioSaavnSourcedTrack.fetchSiblings(query: query, ref: ref),
AudioSource.invidious => AudioSource.invidious =>
InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref), InvidiousSourcedTrack.fetchSiblings(query: query, ref: ref),
AudioSource.dabMusic =>
DABMusicSourcedTrack.fetchSiblings(query: query, ref: ref),
}; };
} }
@ -170,7 +157,7 @@ abstract class SourcedTrack extends BasicSourcedTrack {
/// ///
/// If no sources match the codec, it will return the first or last source /// If no sources match the codec, it will return the first or last source
/// based on the user's audio quality preference. /// based on the user's audio quality preference.
TrackSource? getSourceOfCodec(SourceCodecs codec) { String? getUrlOfCodec(SourceCodecs codec) {
final preferences = ref.read(userPreferencesProvider); final preferences = ref.read(userPreferencesProvider);
final exactMatch = sources.firstWhereOrNull( final exactMatch = sources.firstWhereOrNull(
@ -179,7 +166,7 @@ abstract class SourcedTrack extends BasicSourcedTrack {
); );
if (exactMatch != null) { if (exactMatch != null) {
return exactMatch; return exactMatch.url;
} }
final sameCodecSources = sources final sameCodecSources = sources
@ -193,8 +180,8 @@ abstract class SourcedTrack extends BasicSourcedTrack {
if (sameCodecSources.isNotEmpty) { if (sameCodecSources.isNotEmpty) {
return preferences.audioQuality > SourceQualities.low return preferences.audioQuality > SourceQualities.low
? sameCodecSources.first ? sameCodecSources.first.url
: sameCodecSources.last; : sameCodecSources.last.url;
} }
final fallbackSource = sources.sorted((a, b) { final fallbackSource = sources.sorted((a, b) {
@ -204,24 +191,23 @@ abstract class SourcedTrack extends BasicSourcedTrack {
}); });
return preferences.audioQuality > SourceQualities.low return preferences.audioQuality > SourceQualities.low
? fallbackSource.firstOrNull ? fallbackSource.firstOrNull?.url
: fallbackSource.lastOrNull; : fallbackSource.lastOrNull?.url;
}
String? getUrlOfCodec(SourceCodecs codec) {
return getSourceOfCodec(codec)?.url;
} }
SourceCodecs get codec { SourceCodecs get codec {
final preferences = ref.read(userPreferencesProvider); final preferences = ref.read(userPreferencesProvider);
return switch (preferences.audioSource) { return preferences.audioSource == AudioSource.jiosaavn
AudioSource.dabMusic => ? SourceCodecs.m4a
preferences.audioQuality == SourceQualities.uncompressed : preferences.streamMusicCodec;
? SourceCodecs.flac }
: SourceCodecs.mp3,
AudioSource.jiosaavn => SourceCodecs.m4a, TrackSource get activeTrackSource {
_ => preferences.streamMusicCodec final audioQuality = ref.read(userPreferencesProvider).audioQuality;
}; return sources.firstWhereOrNull(
(source) => source.codec == codec && source.quality == audioQuality,
) ??
sources.first;
} }
} }

View File

@ -1,303 +0,0 @@
import 'dart:convert';
import 'package:collection/collection.dart';
import 'package:dio/dio.dart';
import 'package:drift/drift.dart';
import 'package:flutter_riverpod/flutter_riverpod.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/logger/logger.dart';
import 'package:spotube/services/sourced_track/enums.dart';
import 'package:spotube/services/sourced_track/exceptions.dart';
import 'package:spotube/services/sourced_track/sourced_track.dart';
import 'package:dab_music_api/dab_music_api.dart';
final dabMusicApiClient = DabMusicApiClient(
Dio(),
baseUrl: "https://dab.yeet.su/api",
);
/// Only Music source that can't support database caching due to having no endpoint.
/// But ISRC search is 100% reliable so caching is actually not necessary.
class DABMusicSourcedTrack extends SourcedTrack {
DABMusicSourcedTrack({
required super.ref,
required super.source,
required super.siblings,
required super.info,
required super.query,
required super.sources,
});
static Future<SourcedTrack> fetchFromTrack({
required TrackSourceQuery query,
required Ref ref,
}) async {
try {
final database = ref.read(databaseProvider);
final cachedSource = await (database.select(database.sourceMatchTable)
..where((s) => s.trackId.equals(query.id))
..limit(1)
..orderBy([
(s) => OrderingTerm(
expression: s.createdAt,
mode: OrderingMode.desc,
),
]))
.get()
.then((s) => s.firstOrNull);
if (cachedSource != null &&
cachedSource.sourceType == SourceType.dabMusic) {
final json = jsonDecode(cachedSource.sourceId);
final info = TrackSourceInfo.fromJson(json["info"]);
final source = (json["sources"] as List?)
?.map((s) => TrackSource.fromJson(s))
.toList();
final [updatedSource] = await fetchSources(
info.id,
ref.read(userPreferencesProvider).audioQuality,
const AudioQuality(
isHiRes: true,
maximumBitDepth: 16,
maximumSamplingRate: 44.1,
),
);
return DABMusicSourcedTrack(
ref: ref,
source: AudioSource.dabMusic,
siblings: [],
info: info,
query: query,
sources: [
source!.first.copyWith(url: updatedSource.url),
],
);
}
final siblings = await fetchSiblings(ref: ref, query: query);
if (siblings.isEmpty) {
throw TrackNotFoundError(query);
}
await database.into(database.sourceMatchTable).insert(
SourceMatchTableCompanion.insert(
trackId: query.id,
sourceId: jsonEncode({
"info": siblings.first.info.toJson(),
"sources": (siblings.first.source ?? [])
.map((s) => s.toJson())
.toList(),
}),
sourceType: const Value(SourceType.dabMusic),
),
);
return DABMusicSourcedTrack(
ref: ref,
siblings: siblings.map((s) => s.info).skip(1).toList(),
sources: siblings.first.source!,
info: siblings.first.info,
query: query,
source: AudioSource.dabMusic,
);
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
rethrow;
}
}
static Future<List<TrackSource>> fetchSources(
String id,
SourceQualities quality,
AudioQuality trackMaximumQuality,
) async {
try {
final isUncompressed = quality == SourceQualities.uncompressed;
final streamResponse = await dabMusicApiClient.music.getStream(
trackId: id,
quality: isUncompressed ? "27" : "5",
);
if (streamResponse.url == null) {
throw Exception("No stream URL found for track ID: $id");
}
// kbps = (bitDepth * sampleRate * channels) / 1000
final uncompressedBitrate = !isUncompressed
? 0
: ((trackMaximumQuality.maximumBitDepth ?? 0) *
((trackMaximumQuality.maximumSamplingRate ?? 0) * 1000) *
2) /
1000;
return [
TrackSource(
url: streamResponse.url!,
quality: isUncompressed
? SourceQualities.uncompressed
: SourceQualities.high,
bitrate:
isUncompressed ? "${uncompressedBitrate.floor()}kbps" : "320kbps",
codec: isUncompressed ? SourceCodecs.flac : SourceCodecs.mp3,
qualityLabel: isUncompressed
? "${trackMaximumQuality.maximumBitDepth}bit • ${trackMaximumQuality.maximumSamplingRate}kHz • FLAC • Stereo"
: "MP3 • 320kbps • mp3 • Stereo",
),
];
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
rethrow;
}
}
static Future<SiblingType> toSiblingType(
Ref ref,
int index,
Track result,
) async {
try {
List<TrackSource>? source;
if (index == 0) {
source = await fetchSources(
result.id.toString(),
ref.read(userPreferencesProvider).audioQuality,
result.audioQuality!,
);
}
final SiblingType sibling = (
info: TrackSourceInfo(
artists: result.artist!,
durationMs: Duration(seconds: result.duration!).inMilliseconds,
id: result.id.toString(),
pageUrl: "https://dab.yeet.su/music/${result.id}",
thumbnail: result.albumCover!,
title: result.title!,
),
source: source,
);
return sibling;
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
rethrow;
}
}
static Future<List<SiblingType>> fetchSiblings({
required TrackSourceQuery query,
required Ref ref,
}) async {
try {
List<Track> results = [];
if (query.isrc.isNotEmpty) {
final res =
await dabMusicApiClient.music.getSearch(q: query.isrc, limit: 1);
results = res.tracks ?? <Track>[];
}
if (results.isEmpty) {
final res = await dabMusicApiClient.music.getSearch(
q: SourcedTrack.getSearchTerm(query),
limit: 5,
);
results = res.tracks ?? <Track>[];
}
if (results.isEmpty) {
return [];
}
final matchedResults =
results.mapIndexed((index, d) => toSiblingType(ref, index, d));
return Future.wait(matchedResults);
} catch (e, stackTrace) {
AppLogger.reportError(e, stackTrace);
rethrow;
}
}
@override
Future<DABMusicSourcedTrack> copyWithSibling() async {
if (siblings.isNotEmpty) {
return this;
}
final fetchedSiblings = await fetchSiblings(ref: ref, query: query);
return DABMusicSourcedTrack(
ref: ref,
siblings: fetchedSiblings
.where((s) => s.info.id != info.id)
.map((s) => s.info)
.toList(),
source: source,
info: info,
query: query,
sources: sources,
);
}
@override
Future<DABMusicSourcedTrack?> swapWithSibling(TrackSourceInfo sibling) async {
if (sibling.id == this.info.id) {
return null;
}
// a sibling source that was fetched from the search results
final isStepSibling = siblings.none((s) => s.id == sibling.id);
final newSourceInfo = isStepSibling
? sibling
: siblings.firstWhere((s) => s.id == sibling.id);
final newSiblings = siblings.where((s) => s.id != sibling.id).toList()
..insert(0, this.info);
final source = await fetchSources(
sibling.id,
ref.read(userPreferencesProvider).audioQuality,
const AudioQuality(
isHiRes: true,
maximumBitDepth: 16,
maximumSamplingRate: 44.1,
),
);
final database = ref.read(databaseProvider);
await database.into(database.sourceMatchTable).insert(
SourceMatchTableCompanion.insert(
trackId: query.id,
sourceId: jsonEncode({
"info": newSourceInfo.toJson(),
"sources": source.map((s) => s.toJson()).toList(),
}),
sourceType: const Value(SourceType.dabMusic),
// Because we're sorting by createdAt in the query
// we have to update it to indicate priority
createdAt: Value(DateTime.now()),
),
mode: InsertMode.replace,
);
return DABMusicSourcedTrack(
ref: ref,
siblings: newSiblings,
sources: source,
info: newSourceInfo,
query: query,
source: AudioSource.dabMusic,
);
}
@override
Future<SourcedTrack> refreshStream() async {
// There's no need to refresh the stream for DABMusicSourcedTrack
return this;
}
}

View File

@ -94,7 +94,6 @@ class InvidiousSourcedTrack extends SourcedTrack {
static List<TrackSource> toSources(InvidiousVideoResponse manifest) { static List<TrackSource> toSources(InvidiousVideoResponse manifest) {
return manifest.adaptiveFormats.map((stream) { return manifest.adaptiveFormats.map((stream) {
var isWebm = stream.type.contains("audio/webm");
return TrackSource( return TrackSource(
url: stream.url.toString(), url: stream.url.toString(),
quality: switch (stream.qualityLabel) { quality: switch (stream.qualityLabel) {
@ -102,11 +101,10 @@ class InvidiousSourcedTrack extends SourcedTrack {
"medium" => SourceQualities.medium, "medium" => SourceQualities.medium,
_ => SourceQualities.low, _ => SourceQualities.low,
}, },
codec: isWebm ? SourceCodecs.weba : SourceCodecs.m4a, codec: stream.type.contains("audio/webm")
? SourceCodecs.weba
: SourceCodecs.m4a,
bitrate: stream.bitrate, bitrate: stream.bitrate,
qualityLabel:
"${isWebm ? "Opus" : "AAC"}${stream.bitrate.replaceAll("kbps", "")}kbps "
"${isWebm ? "weba" : "m4a"} • Stereo",
); );
}).toList(); }).toList();
} }

View File

@ -104,7 +104,6 @@ class JioSaavnSourcedTrack extends SourcedTrack {
: SourceQualities.low, : SourceQualities.low,
codec: SourceCodecs.m4a, codec: SourceCodecs.m4a,
bitrate: link.quality, bitrate: link.quality,
qualityLabel: "AAC • ${link.quality} • MP4 • Stereo",
); );
}).toList() }).toList()
); );

View File

@ -98,7 +98,6 @@ class PipedSourcedTrack extends SourcedTrack {
static List<TrackSource> toSources(PipedStreamResponse manifest) { static List<TrackSource> toSources(PipedStreamResponse manifest) {
return manifest.audioStreams.map((audio) { return manifest.audioStreams.map((audio) {
final isMp4 = audio.format == PipedAudioStreamFormat.m4a;
return TrackSource( return TrackSource(
url: audio.url.toString(), url: audio.url.toString(),
quality: switch (audio.quality) { quality: switch (audio.quality) {
@ -106,11 +105,10 @@ class PipedSourcedTrack extends SourcedTrack {
"medium" => SourceQualities.medium, "medium" => SourceQualities.medium,
_ => SourceQualities.low, _ => SourceQualities.low,
}, },
codec: isMp4 ? SourceCodecs.m4a : SourceCodecs.weba, codec: audio.format == PipedAudioStreamFormat.m4a
? SourceCodecs.m4a
: SourceCodecs.weba,
bitrate: audio.bitrate.toString(), bitrate: audio.bitrate.toString(),
qualityLabel:
"${isMp4 ? "AAC" : "Opus"}${(audio.bitrate / 1000).floor()}kbps "
"${isMp4 ? "m4a" : "weba"} • Stereo",
); );
}).toList(); }).toList();
} }

View File

@ -98,7 +98,6 @@ class YoutubeSourcedTrack extends SourcedTrack {
static List<TrackSource> toTrackSources(StreamManifest manifest) { static List<TrackSource> toTrackSources(StreamManifest manifest) {
return manifest.audioOnly.map((streamInfo) { return manifest.audioOnly.map((streamInfo) {
var isWebm = streamInfo.codec.mimeType == "audio/webm";
return TrackSource( return TrackSource(
url: streamInfo.url.toString(), url: streamInfo.url.toString(),
quality: switch (streamInfo.qualityLabel) { quality: switch (streamInfo.qualityLabel) {
@ -107,11 +106,10 @@ class YoutubeSourcedTrack extends SourcedTrack {
"low" => SourceQualities.low, "low" => SourceQualities.low,
_ => SourceQualities.high, _ => SourceQualities.high,
}, },
codec: isWebm ? SourceCodecs.weba : SourceCodecs.m4a, codec: streamInfo.codec.mimeType == "audio/webm"
? SourceCodecs.weba
: SourceCodecs.m4a,
bitrate: streamInfo.bitrate.bitsPerSecond.toString(), bitrate: streamInfo.bitrate.bitsPerSecond.toString(),
qualityLabel:
"${isWebm ? "Opus" : "AAC"}${(streamInfo.bitrate.kiloBitsPerSecond).floor()}kbps "
"${isWebm ? "weba" : "m4a"} • Stereo",
); );
}).toList(); }).toList();
} }

View File

@ -315,7 +315,7 @@ packages:
source: hosted source: hosted
version: "1.3.1" version: "1.3.1"
change_case: change_case:
dependency: "direct main" dependency: transitive
description: description:
name: change_case name: change_case
sha256: f4e08feaa845e75e4f5ad2b0e15f24813d7ea6c27e7b78252f0c17f752cf1157 sha256: f4e08feaa845e75e4f5ad2b0e15f24813d7ea6c27e7b78252f0c17f752cf1157
@ -458,15 +458,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.0.2" version: "1.0.2"
dab_music_api:
dependency: "direct main"
description:
path: "."
ref: main
resolved-ref: "55f96368b7465eec2e5e81774f9f2a7b18acc4ab"
url: "https://github.com/KRTirtho/dab_music_api.git"
source: git
version: "0.1.0"
dart_des: dart_des:
dependency: transitive dependency: transitive
description: description:
@ -972,7 +963,7 @@ packages:
description: description:
path: "." path: "."
ref: HEAD ref: HEAD
resolved-ref: d4d71545111c8ca6c91f0040091c42d74cce1762 resolved-ref: "916bde44cbead75125e8db842eb46bdcf211a79a"
url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git" url: "https://github.com/KRTirtho/flutter_new_pipe_extractor.git"
source: git source: git
version: "0.1.0" version: "0.1.0"
@ -1412,10 +1403,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: invidious name: invidious
sha256: "0da8ebc4c4110057f03302bbd54514b10642154d7be569e7994172f2202dcfe8" sha256: "27ef3a001df875665de15535dbc9099f44d12a59480018fb1e17377d4af0308d"
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.2" version: "0.1.1"
io: io:
dependency: "direct dev" dependency: "direct dev"
description: description:
@ -1444,10 +1435,10 @@ packages:
dependency: "direct main" dependency: "direct main"
description: description:
name: jiosaavn name: jiosaavn
sha256: b6bde15c56398ebfd439825a64fb540a265773d1a518ba103e79988e13d16e1d sha256: d32b4f43f26488f942f5d7d19d748a1f2664ae3d41ff9c7d50eeb81705174bd2
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "0.1.1" version: "0.1.0"
jovial_misc: jovial_misc:
dependency: transitive dependency: transitive
description: description:
@ -2055,14 +2046,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "4.1.0" version: "4.1.0"
retrofit:
dependency: transitive
description:
name: retrofit
sha256: "699cf44ec6c7fc7d248740932eca75d334e36bdafe0a8b3e9ff93100591c8a25"
url: "https://pub.dev"
source: hosted
version: "4.7.2"
riverpod: riverpod:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -24,10 +24,6 @@ dependencies:
bonsoir: ^5.1.10 bonsoir: ^5.1.10
cached_network_image: ^3.3.1 cached_network_image: ^3.3.1
connectivity_plus: ^6.1.2 connectivity_plus: ^6.1.2
dab_music_api:
git:
url: https://github.com/KRTirtho/dab_music_api.git
ref: main
desktop_webview_window: desktop_webview_window:
git: git:
path: packages/desktop_webview_window path: packages/desktop_webview_window
@ -81,8 +77,8 @@ dependencies:
http: ^1.2.1 http: ^1.2.1
image_picker: ^1.1.0 image_picker: ^1.1.0
intl: any intl: any
invidious: ^0.1.2 invidious: ^0.1.1
jiosaavn: ^0.1.1 jiosaavn: ^0.1.0
json_annotation: ^4.8.1 json_annotation: ^4.8.1
local_notifier: ^0.1.6 local_notifier: ^0.1.6
logger: ^2.0.2 logger: ^2.0.2
@ -167,7 +163,6 @@ dependencies:
get_it: ^8.0.3 get_it: ^8.0.3
flutter_markdown_plus: ^1.0.3 flutter_markdown_plus: ^1.0.3
pub_semver: ^2.2.0 pub_semver: ^2.2.0
change_case: ^1.1.0
dev_dependencies: dev_dependencies:
build_runner: ^2.4.13 build_runner: ^2.4.13

View File

@ -1,176 +1,5 @@
{ {
"ar": [
"source",
"uncompressed",
"dab_music_source_description"
],
"bn": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ca": [
"source",
"uncompressed",
"dab_music_source_description"
],
"cs": [
"source",
"uncompressed",
"dab_music_source_description"
],
"de": [
"source",
"uncompressed",
"dab_music_source_description"
],
"es": [
"source",
"uncompressed",
"dab_music_source_description"
],
"eu": [
"source",
"uncompressed",
"dab_music_source_description"
],
"fa": [
"source",
"uncompressed",
"dab_music_source_description"
],
"fi": [
"source",
"uncompressed",
"dab_music_source_description"
],
"fr": [
"source",
"uncompressed",
"dab_music_source_description"
],
"hi": [
"source",
"uncompressed",
"dab_music_source_description"
],
"id": [
"source",
"uncompressed",
"dab_music_source_description"
],
"it": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ja": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ka": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ko": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ne": [
"source",
"uncompressed",
"dab_music_source_description"
],
"nl": [ "nl": [
"audio_source", "audio_source"
"source",
"uncompressed",
"dab_music_source_description"
],
"pl": [
"source",
"uncompressed",
"dab_music_source_description"
],
"pt": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ru": [
"source",
"uncompressed",
"dab_music_source_description"
],
"ta": [
"source",
"uncompressed",
"dab_music_source_description"
],
"th": [
"source",
"uncompressed",
"dab_music_source_description"
],
"tl": [
"source",
"uncompressed",
"dab_music_source_description"
],
"tr": [
"source",
"uncompressed",
"dab_music_source_description"
],
"uk": [
"source",
"uncompressed",
"dab_music_source_description"
],
"vi": [
"source",
"uncompressed",
"dab_music_source_description"
],
"zh": [
"source",
"uncompressed",
"dab_music_source_description"
],
"zh_TW": [
"source",
"uncompressed",
"dab_music_source_description"
] ]
} }

View File

@ -17,7 +17,7 @@
"@types/react": "^19.1.9", "@types/react": "^19.1.9",
"@types/react-dom": "^19.1.7", "@types/react-dom": "^19.1.7",
"astro": "^5.12.8", "astro": "^5.12.8",
"astro-pagefind": "1.8.3", "astro-pagefind": "^1.8.3",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"markdown-it": "^14.1.0", "markdown-it": "^14.1.0",
"react": "^19.1.1", "react": "^19.1.1",

View File

@ -33,7 +33,7 @@ importers:
specifier: ^5.12.8 specifier: ^5.12.8
version: 5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2) version: 5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2)
astro-pagefind: astro-pagefind:
specifier: 1.8.3 specifier: ^1.8.3
version: 1.8.3(astro@5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2)) version: 1.8.3(astro@5.12.8(@types/node@24.1.0)(jiti@2.5.1)(lightningcss@1.30.1)(rollup@4.46.2)(typescript@5.9.2))
date-fns: date-fns:
specifier: ^4.1.0 specifier: ^4.1.0

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 691 B

After

Width:  |  Height:  |  Size: 390 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

After

Width:  |  Height:  |  Size: 777 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

After

Width:  |  Height:  |  Size: 89 KiB

View File

@ -0,0 +1,349 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
viewBox="0 0 762 762"
version="1.1"
id="svg270"
sodipodi:docname="spotube-logo.svg"
inkscape:version="1.2.2 (b0a8486541, 2022-12-01)"
xml:space="preserve"
inkscape:export-filename="spotube-logo.png"
inkscape:export-xdpi="96"
inkscape:export-ydpi="96"
width="762"
height="762"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:bx="https://boxy-svg.com"><sodipodi:namedview
id="namedview272"
pagecolor="#ffffff"
bordercolor="#000000"
borderopacity="0.25"
inkscape:showpageshadow="2"
inkscape:pageopacity="0.0"
inkscape:pagecheckerboard="0"
inkscape:deskcolor="#d1d1d1"
showgrid="false"
inkscape:zoom="0.76199998"
inkscape:cx="194.22573"
inkscape:cy="314.96064"
inkscape:window-width="1920"
inkscape:window-height="1001"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:current-layer="svg270"
inkscape:lockguides="false"><inkscape:page
x="0"
y="0"
width="762"
height="762"
id="page3136" /><inkscape:page
x="640.44641"
y="132.29141"
width="89.999939"
height="89.999985"
id="page3138" /></sodipodi:namedview><defs
id="defs220"><linearGradient
inkscape:collect="always"
id="linearGradient5535"><stop
style="stop-color:#00063b;stop-opacity:1;"
offset="0.25885531"
id="stop5531" /><stop
style="stop-color:#004256;stop-opacity:1;"
offset="1"
id="stop5533" /></linearGradient><linearGradient
id="linearGradient2809"><stop
offset="0.113"
style="stop-color:#5668ea;stop-opacity:1;"
id="stop2803" /><stop
offset="0.60799998"
style="stop-color:#0093b1;stop-opacity:1;"
id="stop2805" /><stop
offset="0.94400001"
style="stop-color:#00a29f;stop-opacity:1;"
id="stop2807" /></linearGradient><linearGradient
id="linearGradient938"><stop
offset="0.113"
style="stop-color:#5869eb;stop-opacity:1;"
id="stop932" /><stop
offset="0.60799998"
style="stop-color:#0093b1;stop-opacity:1;"
id="stop934" /><stop
offset="0.94400001"
style="stop-color:#02a7a4;stop-opacity:1;"
id="stop936" /></linearGradient><radialGradient
id="gradient-2-0"
gradientUnits="userSpaceOnUse"
cx="251.179"
cy="248.821"
r="241.45"
gradientTransform="translate(-1.768285,0.589104)"
xlink:href="#gradient-2" /><linearGradient
id="gradient-2"><stop
offset="0.841"
style="stop-color: rgb(255, 255, 255);"
id="stop169" /><stop
offset="1"
style="stop-color: rgb(201, 201, 201);"
id="stop171" /></linearGradient><filter
id="drop-shadow-filter-0"
x="-0.050892502"
y="-0.050892502"
width="1.1017849"
height="1.1017849"
bx:preset="drop-shadow 1 0 0 10 0.42 rgba(201,201,201,1)"><feGaussianBlur
in="SourceAlpha"
stdDeviation="10"
id="feGaussianBlur174" /><feOffset
dx="0"
dy="0"
id="feOffset176" /><feComponentTransfer
result="offsetblur"
id="feComponentTransfer179"><feFuncA
id="spread-ctrl"
type="linear"
slope="0.84" /></feComponentTransfer><feFlood
flood-color="rgba(201,201,201,1)"
id="feFlood181" /><feComposite
in2="offsetblur"
operator="in"
id="feComposite183" /><feMerge
id="feMerge189"><feMergeNode
id="feMergeNode185" /><feMergeNode
in="SourceGraphic"
id="feMergeNode187" /></feMerge></filter><linearGradient
id="gradient-4-3"
gradientUnits="userSpaceOnUse"
x1="47.146"
y1="18.044001"
x2="47.146"
y2="75.353996"
xlink:href="#gradient-4" /><linearGradient
id="gradient-4"><stop
offset="0.113"
style="stop-color: rgb(83, 240, 111);"
id="stop193" /><stop
offset="0.608"
style="stop-color: rgb(0, 177, 86);"
id="stop195" /><stop
offset="0.944"
style="stop-color: rgb(2, 167, 156);"
id="stop197" /></linearGradient><filter
id="inner-shadow-filter-0"
x="-0.064836091"
y="-0.071329232"
width="1.1296722"
height="1.108079"
bx:preset="inner-shadow 1 0 0 4 0.5 rgba(0,0,0,0.7)"><feOffset
dx="0"
dy="0"
id="feOffset200" /><feGaussianBlur
stdDeviation="4"
id="feGaussianBlur202"
result="result1" /><feComposite
operator="out"
in="SourceGraphic"
in2="result1"
id="feComposite204" /><feComponentTransfer
result="choke"
id="feComponentTransfer208"><feFuncA
type="linear"
slope="1"
id="feFuncA206" /></feComponentTransfer><feFlood
flood-color="rgba(0,0,0,0.7)"
result="color"
id="feFlood210" /><feComposite
operator="in"
in="color"
in2="choke"
result="shadow"
id="feComposite212" /><feComposite
operator="over"
in="shadow"
in2="SourceGraphic"
id="feComposite214" /></filter><linearGradient
id="gradient-4-1"
gradientUnits="userSpaceOnUse"
x1="82.026001"
y1="144.832"
x2="82.026001"
y2="264.46201"
xlink:href="#linearGradient2809"
gradientTransform="translate(7.2213312)" /><linearGradient
id="gradient-4-2"
gradientUnits="userSpaceOnUse"
x1="143.69299"
y1="22.804001"
x2="143.69299"
y2="264.582"
xlink:href="#linearGradient938" /><linearGradient
id="gradient-4-0"
gradientUnits="userSpaceOnUse"
x1="205.862"
y1="146.28"
x2="205.862"
y2="265.91"
xlink:href="#gradient-4"
gradientTransform="translate(-7.2213312)" /><filter
style="color-interpolation-filters:sRGB"
inkscape:label="Drop Shadow"
id="filter2000"
x="-0.3425389"
y="-0.3425389"
width="1.6850778"
height="1.6850778"><feFlood
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
id="feFlood1990" /><feComposite
in="flood"
in2="SourceGraphic"
operator="out"
result="composite1"
id="feComposite1992" /><feGaussianBlur
in="composite1"
stdDeviation="29.980818"
result="blur"
id="feGaussianBlur1994" /><feOffset
dx="0"
dy="0"
result="offset"
id="feOffset1996" /><feComposite
in="offset"
in2="SourceGraphic"
operator="atop"
result="fbSourceGraphic"
id="feComposite1998" /><feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix2062" /><feFlood
id="feFlood2064"
flood-opacity="1"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" /><feComposite
in2="fbSourceGraphic"
id="feComposite2066"
in="flood"
operator="out"
result="composite1" /><feGaussianBlur
id="feGaussianBlur2068"
in="composite1"
stdDeviation="28.6433"
result="blur" /><feOffset
id="feOffset2070"
dx="0"
dy="0"
result="offset" /><feComposite
in2="fbSourceGraphic"
id="feComposite2072"
in="offset"
operator="atop"
result="fbSourceGraphic" /><feColorMatrix
result="fbSourceGraphicAlpha"
in="fbSourceGraphic"
values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"
id="feColorMatrix3393" /><feFlood
id="feFlood3395"
flood-opacity="0.352941"
flood-color="rgb(0,0,0)"
result="flood"
in="fbSourceGraphic" /><feComposite
in2="fbSourceGraphic"
id="feComposite3397"
in="flood"
operator="in"
result="composite1" /><feGaussianBlur
id="feGaussianBlur3399"
in="composite1"
stdDeviation="6.59891"
result="blur" /><feOffset
id="feOffset3401"
dx="0"
dy="0"
result="offset" /><feComposite
in2="offset"
id="feComposite3403"
in="fbSourceGraphic"
operator="over"
result="composite2" /></filter><linearGradient
inkscape:collect="always"
xlink:href="#linearGradient2809"
id="linearGradient5506"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(117.34662)"
x1="82.026001"
y1="144.832"
x2="82.026001"
y2="264.46201" /><radialGradient
inkscape:collect="always"
xlink:href="#linearGradient5535"
id="radialGradient5537"
cx="143.6935"
cy="143.69299"
fx="143.6935"
fy="143.69299"
r="152.72653"
gradientTransform="matrix(1,0,0,0.8506841,0,21.45565)"
gradientUnits="userSpaceOnUse" /></defs><circle
style="opacity:1;fill:#242832;fill-opacity:1;stroke:#000000;stroke-width:10;stroke-dasharray:none;stroke-opacity:0.961795;filter:url(#filter2000)"
id="path1157"
cx="381.48901"
cy="381.48901"
inkscape:label="path1157"
r="235.79112"
sodipodi:insensitive="true" /><g
transform="matrix(0.319972,0,0,0.323174,379.08153,437.03375)"
id="g228"><g
style="opacity:1;fill:none;fill-rule:nonzero;stroke:none;stroke-width:0;stroke-linecap:butt;stroke-linejoin:miter;stroke-miterlimit:10;stroke-dasharray:none"
transform="matrix(3.89,0,0,3.89,-175.05,-175.05)"
id="g226" /></g><g
id="g236"
style="fill:none;filter:url(#inner-shadow-filter-0)"
transform="matrix(1.107829,0,0,1.106267,221.95533,199.03714)"><path
d="m 78.642332,155.437 v 98.42 c 0,5.867 4.741,10.605 10.605,10.605 5.854,0 10.604995,-4.738 10.604995,-10.605 v -98.42 c 0,-5.856 -4.750995,-10.605 -10.604995,-10.605 -5.864,0 -10.605,4.744 -10.605,10.605 z"
style="fill:none;fill-opacity:1;stroke:url(#gradient-4-1);stroke-width:9.80924px;stroke-linecap:round;stroke-linejoin:round"
id="path230" /><path
d="m 29.456,264.582 h 23.351 v -116.85 c 0.064,-0.56 0.166,-1.119 0.166,-1.693 0,-50.412 40.69,-91.42 90.698,-91.42 50.002,0 90.692,41.008 90.692,91.42 0,0.771 0.113,1.518 0.228,2.263 v 116.28 h 23.354 c 16.254,0 29.442,-13.64 29.442,-30.469 v -60.936 c 0,-13.878 -8.989,-25.57 -21.261,-29.249 C 264.997,76.957 210.518,22.804 143.676,22.804 76.816,22.804 22.329,76.962 21.211,143.954 8.956,147.638 0,159.32 0,173.187 v 60.926 c 0,16.819 13.187,30.469 29.456,30.469 z"
style="fill:url(#radialGradient5537);fill-opacity:1;stroke:url(#gradient-4-2);stroke-width:18.0661;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none"
id="path232" /><path
d="M 49.735541,279.35822 C 23.7214,267.48486 38.122112,248.62719 80.85964,237.45225 c 14.400662,-3.49216 25.08508,-5.12184 43.66659,-4.88901 11.61348,0.23282 24.62053,3.49216 24.62053,3.49216 0,-42.13877 -0.46471,-121.7601 -0.46471,-160.872338 4.6454,0 7.89719,-0.232827 14.40071,-0.232827 0,2.328107 0,4.190613 0,6.053093 0,2.095305 0,3.259358 0.46471,4.656212 4.6454,14.66709 11.14893,20.48736 43.66659,38.41381 41.34392,23.04827 53.42195,36.78411 53.42195,55.17616 -0.46471,17.22802 -30.65954,54.01213 -37.16306,52.61528 9.29075,-13.03741 22.2978,-27.00606 25.54958,-38.64661 4.18085,-14.20147 -7.43263,-34.2232 -26.01414,-44.69971 -14.86522,-8.8468 -50.17016,-16.52957 -59.92547,-16.52957 0,0 -0.46472,84.74317 -0.46472,116.87109 0,5.35464 -9.7553,14.89989 -15.32977,18.15925 -25.54958,15.36551 -75.25519,22.34984 -97.553043,12.33896 z"
id="path3079"
style="stroke-width:3.28861" /><path
d="m 188.76763,155.437 v 98.42 c 0,5.867 4.741,10.605 10.60501,10.605 5.854,0 10.605,-4.738 10.605,-10.605 v -98.42 c 0,-5.856 -4.751,-10.605 -10.605,-10.605 -5.86401,0 -10.60501,4.744 -10.60501,10.605 z"
style="fill:none;stroke:url(#linearGradient5506);stroke-width:9.80924px;stroke-linecap:round;stroke-linejoin:round"
id="path5502" /></g><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g240" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g242" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g244" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g246" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g248" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g250" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g252" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g254" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g256" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g258" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g260" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g262" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g264" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g266" /><g
transform="matrix(0.972684,0,0,0.972684,193.06382,142.14148)"
id="g268" /></svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -1,109 +1,104 @@
import type { IconType } from "react-icons"; import type { IconType } from "react-icons";
import { import {
FaAndroid, FaAndroid,
FaApple, FaApple,
FaDebian, FaDebian,
FaFedora, FaFedora,
FaOpensuse, FaOpensuse,
FaUbuntu, FaUbuntu,
FaWindows, FaWindows,
FaRedhat, FaRedhat,
} from "react-icons/fa6"; } from "react-icons/fa6";
import { LuHouse, LuNewspaper, LuDownload, LuBook } from "react-icons/lu"; import { LuHouse, LuNewspaper, LuDownload, LuBook } from "react-icons/lu";
export const routes: Record<string, [string, IconType | null]> = { export const routes: Record<string, [string, IconType|null]> = {
"/": ["Home", LuHouse], "/": ["Home", LuHouse],
"/blog": ["Blog", LuNewspaper], "/blog": ["Blog", LuNewspaper],
"/docs": ["Docs", LuBook], "/docs": ["Docs", LuBook],
"/downloads": ["Downloads", LuDownload], "/downloads": ["Downloads", LuDownload],
"/about": ["About", null], "/about": ["About", null],
}; };
const releasesUrl = const releasesUrl =
"https://github.com/KRTirtho/Spotube/releases/latest/download"; "https://github.com/KRTirtho/Spotube/releases/latest/download";
export const downloadLinks: Record<string, [string, IconType[]]> = { export const downloadLinks: Record<string, [string, IconType[]]> = {
"Android Apk": [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid]], "Android Apk": [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid]],
"Windows Executable": [ "Windows Executable": [
`${releasesUrl}/Spotube-windows-x86_64-setup.exe`, `${releasesUrl}/Spotube-windows-x86_64-setup.exe`,
[FaWindows], [FaWindows],
], ],
"macOS Dmg": [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple]], "macOS Dmg": [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple]],
"Ubuntu, Debian": [ "Ubuntu, Debian": [
`${releasesUrl}/Spotube-linux-x86_64.deb`, `${releasesUrl}/Spotube-linux-x86_64.deb`,
[FaUbuntu, FaDebian], [FaUbuntu, FaDebian],
], ],
"Fedora, Redhat, Opensuse": [ "Fedora, Redhat, Opensuse": [
`${releasesUrl}/Spotube-linux-x86_64.rpm`, `${releasesUrl}/Spotube-linux-x86_64.rpm`,
[FaFedora, FaRedhat, FaOpensuse], [FaFedora, FaRedhat, FaOpensuse],
], ],
"iPhone Ipa": [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple]], "iPhone Ipa": [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple]],
}; };
export const extendedDownloadLinks: Record< export const extendedDownloadLinks: Record<
string, string,
[string, IconType[], string] [string, IconType[], string]
> = { > = {
Android: [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid], "apk"], Android: [`${releasesUrl}/Spotube-android-all-arch.apk`, [FaAndroid], "apk"],
Windows: [ Windows: [
`${releasesUrl}/Spotube-windows-x86_64-setup.exe`, `${releasesUrl}/Spotube-windows-x86_64-setup.exe`,
[FaWindows], [FaWindows],
"exe", "exe",
], ],
macOS: [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], macOS: [`${releasesUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"],
"Ubuntu, Debian (x64)": [ "Ubuntu, Debian": [
`${releasesUrl}/Spotube-linux-x86_64.deb`, `${releasesUrl}/Spotube-linux-x86_64.deb`,
[FaUbuntu, FaDebian], [FaUbuntu, FaDebian],
"deb", "deb",
], ],
"Ubuntu, Debian (arm64)": [ "Fedora, Redhat, Opensuse": [
`${releasesUrl}/Spotube-linux-aarch64.deb`, `${releasesUrl}/Spotube-linux-x86_64.rpm`,
[FaUbuntu, FaDebian], [FaFedora, FaRedhat, FaOpensuse],
"deb", "rpm",
], ],
// "Fedora, Redhat, Opensuse": [ iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"],
// `${releasesUrl}/Spotube-linux-x86_64.rpm`,
// [FaFedora, FaRedhat, FaOpensuse],
// "rpm",
// ],
iPhone: [`${releasesUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"],
}; };
const nightlyReleaseUrl = const nightlyReleaseUrl =
"https://github.com/KRTirtho/Spotube/releases/download/nightly"; "https://github.com/KRTirtho/Spotube/releases/download/nightly";
export const extendedNightlyDownloadLinks: Record< export const extendedNightlyDownloadLinks: Record<
string, string,
[string, IconType[], string] [string, IconType[], string]
> = { > = {
Android: [ Android: [
`${nightlyReleaseUrl}/Spotube-android-all-arch.apk`, `${nightlyReleaseUrl}/Spotube-android-all-arch.apk`,
[FaAndroid], [FaAndroid],
"apk", "apk",
], ],
Windows: [ Windows: [
`${nightlyReleaseUrl}/Spotube-windows-x86_64-setup.exe`, `${nightlyReleaseUrl}/Spotube-windows-x86_64-setup.exe`,
[FaWindows], [FaWindows],
"exe", "exe",
], ],
macOS: [`${nightlyReleaseUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"], macOS: [`${nightlyReleaseUrl}/Spotube-macos-universal.dmg`, [FaApple], "dmg"],
"Ubuntu, Debian": [ "Ubuntu, Debian": [
`${nightlyReleaseUrl}/Spotube-linux-x86_64.deb`, `${nightlyReleaseUrl}/Spotube-linux-x86_64.deb`,
[FaUbuntu, FaDebian], [FaUbuntu, FaDebian],
"deb", "deb",
], ],
"Fedora, Redhat, Opensuse": [ "Fedora, Redhat, Opensuse": [
`${nightlyReleaseUrl}/Spotube-linux-x86_64.rpm`, `${nightlyReleaseUrl}/Spotube-linux-x86_64.rpm`,
[FaFedora, FaRedhat, FaOpensuse], [FaFedora, FaRedhat, FaOpensuse],
"rpm", "rpm",
], ],
iPhone: [`${nightlyReleaseUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"], iPhone: [`${nightlyReleaseUrl}/Spotube-iOS.ipa`, [FaApple], "ipa"],
}; };
export const ADS_SLOTS = Object.freeze({ export const ADS_SLOTS = Object.freeze({
rootPageDisplay: 5979549631, rootPageDisplay: 5979549631,
blogPageInFeed: 3386010031, blogPageInFeed: 3386010031,
downloadPageDisplay: 9928443050, downloadPageDisplay: 9928443050,
packagePageArticle: 9119323068, packagePageArticle: 9119323068,
// This is being used for rehype-auto-ads in svelte.config.js // This is being used for rehype-auto-ads in svelte.config.js
blogArticlePageArticle: 6788673194, blogArticlePageArticle: 6788673194,

View File

@ -12,7 +12,7 @@ const {
adSlot, adSlot,
adFormat, adFormat,
fullWidthResponsive = true, fullWidthResponsive = true,
style = "display:block", style,
adLayout, adLayout,
adLayoutKey, adLayoutKey,
} = Astro.props; } = Astro.props;
@ -22,7 +22,7 @@ const AD_CLIENT = "ca-pub-6419300932495863";
<ins <ins
class="adsbygoogle" class="adsbygoogle"
style={style} {style}
data-ad-layout={adLayout} data-ad-layout={adLayout}
data-ad-client={AD_CLIENT} data-ad-client={AD_CLIENT}
data-ad-slot={adSlot} data-ad-slot={adSlot}

View File

@ -22,7 +22,34 @@ const otherDownloads: [string, string, IconType][] = [
<br /><br /> <br /><br />
<h5 class="h5">Spotube is available for every platform</h5> <h5 class="h5">Spotube is available for every platform</h5>
<br /> <br />
<DownloadItems links={extendedDownloadLinks} /> <!-- WARNING! -->
<h3 class="h3 text-red-500" data-svelte-h="svelte-1l4b696">
Versions of Spotube (&lt;=v4.0.2) are ceased to work with Spotify™ API.
<br />
So users can no longer use/download those versions.
<br />
Please wait for the next version that will remedy this issue by not using such
APIs.
</h3>
<p class="text-surface-500 mt-5" data-svelte-h="svelte-1nkw9cu">
Spotube has no affiliation with Spotify™ or any of its subsidiaries.
</p>
<br />
<br />
<!-- <DownloadItems links={extendedDownloadLinks} /> -->
<h6 class="h6 mb-5" data-svelte-h="svelte-1ws2638">
The new Spotube v5 is still under beta. Please use the Nightly version
until stable release.
</h6>
<!-- WARNING! -->
<div class="flex">
<a href="/downloads/nightly" class="flex gap-2 btn btn-lg preset-filled">
<LuDownload />
Download Nightly
</a>
</div>
<br />
<br /> <br />
<Ads adSlot={ADS_SLOTS.downloadPageDisplay} adFormat="auto" /> <Ads adSlot={ADS_SLOTS.downloadPageDisplay} adFormat="auto" />
<br /> <br />

View File

@ -53,11 +53,11 @@ import { ADS_SLOTS } from "~/collections/app";
</div> </div>
<div class="flex justify-center"> <div class="flex justify-center">
<a <a
href="/downloads" href="/downloads/nightly"
class="flex gap-2 btn btn-lg preset-filled" class="flex gap-2 btn btn-lg preset-filled"
> >
<LuDownload /> <LuDownload />
Download Download Nightly
</a> </a>
</div> </div>
</div> </div>