🎨 Prefer single quote

This commit is contained in:
LittleSheep 2024-08-27 14:48:31 +08:00
parent e7ea852725
commit 95b04adede
31 changed files with 470 additions and 630 deletions

View File

@ -21,6 +21,7 @@ linter:
# `// ignore_for_file: name_of_lint` syntax on the line or in the file # `// ignore_for_file: name_of_lint` syntax on the line or in the file
# producing the lint. # producing the lint.
rules: rules:
prefer_single_quotes: true
# avoid_print: false # Uncomment to disable the `avoid_print` rule # avoid_print: false # Uncomment to disable the `avoid_print` rule
# prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule

View File

@ -1,192 +0,0 @@
/// GENERATED CODE - DO NOT MODIFY BY HAND
/// *****************************************************
/// FlutterGen
/// *****************************************************
// coverage:ignore-file
// ignore_for_file: type=lint
// ignore_for_file: directives_ordering,unnecessary_import,implicit_dynamic_list_literal,deprecated_member_use
import 'package:flutter/widgets.dart';
class $AssetsLogosGen {
const $AssetsLogosGen();
/// File path: assets/logos/songlink-transparent.png
AssetGenImage get songlinkTransparent =>
const AssetGenImage('assets/logos/songlink-transparent.png');
/// File path: assets/logos/songlink.png
AssetGenImage get songlink =>
const AssetGenImage('assets/logos/songlink.png');
/// List of all assets
List<AssetGenImage> get values => [songlinkTransparent, songlink];
}
class $AssetsTutorialGen {
const $AssetsTutorialGen();
/// File path: assets/tutorial/step-1.png
AssetGenImage get step1 => const AssetGenImage('assets/tutorial/step-1.png');
/// File path: assets/tutorial/step-2.png
AssetGenImage get step2 => const AssetGenImage('assets/tutorial/step-2.png');
/// File path: assets/tutorial/step-3.png
AssetGenImage get step3 => const AssetGenImage('assets/tutorial/step-3.png');
/// List of all assets
List<AssetGenImage> get values => [step1, step2, step3];
}
class Assets {
Assets._();
static const AssetGenImage albumPlaceholder =
AssetGenImage('assets/album-placeholder.png');
static const AssetGenImage bengaliPatternsBg =
AssetGenImage('assets/bengali-patterns-bg.jpg');
static const AssetGenImage branding = AssetGenImage('assets/branding.png');
static const AssetGenImage emptyBox = AssetGenImage('assets/empty_box.png');
static const AssetGenImage jiosaavn = AssetGenImage('assets/jiosaavn.png');
static const AssetGenImage likedTracks =
AssetGenImage('assets/liked-tracks.jpg');
static const $AssetsLogosGen logos = $AssetsLogosGen();
static const AssetGenImage placeholder =
AssetGenImage('assets/placeholder.png');
static const AssetGenImage rhythm_boxHeroBanner =
AssetGenImage('assets/rhythm_box-hero-banner.png');
static const AssetGenImage rhythm_boxLogoForeground =
AssetGenImage('assets/rhythm_box-logo-foreground.jpg');
static const String rhythm_boxLogoIco = 'assets/rhythm_box-logo.ico';
static const AssetGenImage rhythm_boxLogoPng =
AssetGenImage('assets/rhythm_box-logo.png');
static const String rhythm_boxLogoSvg = 'assets/rhythm_box-logo.svg';
static const AssetGenImage rhythm_boxLogoAndroid12 =
AssetGenImage('assets/rhythm_box-logo_android12.png');
static const AssetGenImage rhythm_boxNightlyLogoForeground =
AssetGenImage('assets/rhythm_box-nightly-logo-foreground.jpg');
static const AssetGenImage rhythm_boxNightlyLogoPng =
AssetGenImage('assets/rhythm_box-nightly-logo.png');
static const String rhythm_boxNightlyLogoSvg =
'assets/rhythm_box-nightly-logo.svg';
static const AssetGenImage rhythm_boxNightlyLogoAndroid12 =
AssetGenImage('assets/rhythm_box-nightly-logo_android12.png');
static const AssetGenImage rhythm_boxScreenshot =
AssetGenImage('assets/rhythm_box-screenshot.png');
static const AssetGenImage rhythm_boxTallCapsule =
AssetGenImage('assets/rhythm_box-tall-capsule.png');
static const AssetGenImage rhythm_boxWideCapsuleLarge =
AssetGenImage('assets/rhythm_box-wide-capsule-large.png');
static const AssetGenImage rhythm_boxWideCapsuleSmall =
AssetGenImage('assets/rhythm_box-wide-capsule-small.png');
static const AssetGenImage rhythm_boxBanner =
AssetGenImage('assets/rhythm_box_banner.png');
static const AssetGenImage success = AssetGenImage('assets/success.png');
static const $AssetsTutorialGen tutorial = $AssetsTutorialGen();
static const AssetGenImage userPlaceholder =
AssetGenImage('assets/user-placeholder.png');
/// List of all assets
static List<dynamic> get values => [
albumPlaceholder,
bengaliPatternsBg,
branding,
emptyBox,
jiosaavn,
likedTracks,
placeholder,
rhythm_boxHeroBanner,
rhythm_boxLogoForeground,
rhythm_boxLogoIco,
rhythm_boxLogoPng,
rhythm_boxLogoSvg,
rhythm_boxLogoAndroid12,
rhythm_boxNightlyLogoForeground,
rhythm_boxNightlyLogoPng,
rhythm_boxNightlyLogoSvg,
rhythm_boxNightlyLogoAndroid12,
rhythm_boxScreenshot,
rhythm_boxTallCapsule,
rhythm_boxWideCapsuleLarge,
rhythm_boxWideCapsuleSmall,
rhythm_boxBanner,
success,
userPlaceholder
];
}
class AssetGenImage {
const AssetGenImage(this._assetName);
final String _assetName;
Image image({
Key? key,
AssetBundle? bundle,
ImageFrameBuilder? frameBuilder,
ImageErrorWidgetBuilder? errorBuilder,
String? semanticLabel,
bool excludeFromSemantics = false,
double? scale,
double? width,
double? height,
Color? color,
Animation<double>? opacity,
BlendMode? colorBlendMode,
BoxFit? fit,
AlignmentGeometry alignment = Alignment.center,
ImageRepeat repeat = ImageRepeat.noRepeat,
Rect? centerSlice,
bool matchTextDirection = false,
bool gaplessPlayback = false,
bool isAntiAlias = false,
String? package,
FilterQuality filterQuality = FilterQuality.low,
int? cacheWidth,
int? cacheHeight,
}) {
return Image.asset(
_assetName,
key: key,
bundle: bundle,
frameBuilder: frameBuilder,
errorBuilder: errorBuilder,
semanticLabel: semanticLabel,
excludeFromSemantics: excludeFromSemantics,
scale: scale,
width: width,
height: height,
color: color,
opacity: opacity,
colorBlendMode: colorBlendMode,
fit: fit,
alignment: alignment,
repeat: repeat,
centerSlice: centerSlice,
matchTextDirection: matchTextDirection,
gaplessPlayback: gaplessPlayback,
isAntiAlias: isAntiAlias,
package: package,
filterQuality: filterQuality,
cacheWidth: cacheWidth,
cacheHeight: cacheHeight,
);
}
ImageProvider provider({
AssetBundle? bundle,
String? package,
}) {
return AssetImage(
_assetName,
bundle: bundle,
package: package,
);
}
String get path => _assetName;
String get keyName => _assetName;
}

View File

@ -3,6 +3,6 @@ import 'package:intl/intl.dart';
final compactNumberFormatter = NumberFormat.compact(); final compactNumberFormatter = NumberFormat.compact();
final usdFormatter = NumberFormat.compactCurrency( final usdFormatter = NumberFormat.compactCurrency(
locale: 'en-US', locale: 'en-US',
symbol: r"$", symbol: r'$',
decimalDigits: 2, decimalDigits: 2,
); );

View File

@ -9,7 +9,7 @@ class ISOLanguageName {
@override @override
String toString() { String toString() {
return "$name ($nativeName)"; return '$name ($nativeName)';
} }
} }
@ -41,9 +41,9 @@ abstract class LanguageLocals {
// name: "Amharic", // name: "Amharic",
// nativeName: "አማርኛ", // nativeName: "አማርኛ",
// ), // ),
"ar": const ISOLanguageName( 'ar': const ISOLanguageName(
name: "Arabic", name: 'Arabic',
nativeName: "العربية", nativeName: 'العربية',
), ),
// "an": const ISOLanguageName( // "an": const ISOLanguageName(
// name: "Aragonese", // name: "Aragonese",
@ -81,17 +81,17 @@ abstract class LanguageLocals {
// name: "Bashkir", // name: "Bashkir",
// nativeName: "башҡорт теле", // nativeName: "башҡорт теле",
// ), // ),
"eu": const ISOLanguageName( 'eu': const ISOLanguageName(
name: "Basque", name: 'Basque',
nativeName: "Euskara", nativeName: 'Euskara',
), ),
// "be": const ISOLanguageName( // "be": const ISOLanguageName(
// name: "Belarusian", // name: "Belarusian",
// nativeName: "Беларуская", // nativeName: "Беларуская",
// ), // ),
"bn": const ISOLanguageName( 'bn': const ISOLanguageName(
name: "Bengali", name: 'Bengali',
nativeName: "বাংলা", nativeName: 'বাংলা',
), ),
// "bh": const ISOLanguageName( // "bh": const ISOLanguageName(
// name: "Bihari", // name: "Bihari",
@ -117,9 +117,9 @@ abstract class LanguageLocals {
// name: "Burmese", // name: "Burmese",
// nativeName: "ဗမာစာ", // nativeName: "ဗမာစာ",
// ), // ),
"ca": const ISOLanguageName( 'ca': const ISOLanguageName(
name: "Catalan", name: 'Catalan',
nativeName: "Català", nativeName: 'Català',
), ),
// "ch": const ISOLanguageName( // "ch": const ISOLanguageName(
// name: "Chamorro", // name: "Chamorro",
@ -133,9 +133,9 @@ abstract class LanguageLocals {
// name: "Chichewa", // name: "Chichewa",
// nativeName: "chiCheŵa", // nativeName: "chiCheŵa",
// ), // ),
"zh": const ISOLanguageName( 'zh': const ISOLanguageName(
name: "Simplified Chinese", name: 'Simplified Chinese',
nativeName: "简体中文", nativeName: '简体中文',
), ),
// "cv": const ISOLanguageName( // "cv": const ISOLanguageName(
// name: "Chuvash", // name: "Chuvash",
@ -157,9 +157,9 @@ abstract class LanguageLocals {
// name: "Croatian", // name: "Croatian",
// nativeName: "hrvatski", // nativeName: "hrvatski",
// ), // ),
"cs": const ISOLanguageName( 'cs': const ISOLanguageName(
name: "Czech", name: 'Czech',
nativeName: "česky, čeština", nativeName: 'česky, čeština',
), ),
// "da": const ISOLanguageName( // "da": const ISOLanguageName(
// name: "Danish", // name: "Danish",
@ -169,13 +169,13 @@ abstract class LanguageLocals {
// name: "Maldivian;", // name: "Maldivian;",
// nativeName: "ދިވެހި", // nativeName: "ދިވެހި",
// ), // ),
"nl": const ISOLanguageName( 'nl': const ISOLanguageName(
name: "Dutch", name: 'Dutch',
nativeName: "Nederlands", nativeName: 'Nederlands',
), ),
"en": const ISOLanguageName( 'en': const ISOLanguageName(
name: "English", name: 'English',
nativeName: "English", nativeName: 'English',
), ),
// "eo": const ISOLanguageName( // "eo": const ISOLanguageName(
// name: "Esperanto", // name: "Esperanto",
@ -197,13 +197,13 @@ abstract class LanguageLocals {
// name: "Fijian", // name: "Fijian",
// nativeName: "vosa Vakaviti", // nativeName: "vosa Vakaviti",
// ), // ),
"fi": const ISOLanguageName( 'fi': const ISOLanguageName(
name: "Finnish", name: 'Finnish',
nativeName: "suomi", nativeName: 'suomi',
), ),
"fr": const ISOLanguageName( 'fr': const ISOLanguageName(
name: "French", name: 'French',
nativeName: "français", nativeName: 'français',
), ),
// "ff": const ISOLanguageName( // "ff": const ISOLanguageName(
// name: "Fula; Fulah; Pulaar; Pular", // name: "Fula; Fulah; Pulaar; Pular",
@ -213,13 +213,13 @@ abstract class LanguageLocals {
// name: "Galician", // name: "Galician",
// nativeName: "Galego", // nativeName: "Galego",
// ), // ),
"ka": const ISOLanguageName( 'ka': const ISOLanguageName(
name: "Georgian", name: 'Georgian',
nativeName: "ქართული", nativeName: 'ქართული',
), ),
"de": const ISOLanguageName( 'de': const ISOLanguageName(
name: "German", name: 'German',
nativeName: "Deutsch", nativeName: 'Deutsch',
), ),
// "el": const ISOLanguageName( // "el": const ISOLanguageName(
// name: "Greek, Modern", // name: "Greek, Modern",
@ -249,9 +249,9 @@ abstract class LanguageLocals {
// name: "Herero", // name: "Herero",
// nativeName: "Otjiherero", // nativeName: "Otjiherero",
// ), // ),
"hi": const ISOLanguageName( 'hi': const ISOLanguageName(
name: "Hindi", name: 'Hindi',
nativeName: "हिन्दी, हिंदी", nativeName: 'हिन्दी, हिंदी',
), ),
// "ho": const ISOLanguageName( // "ho": const ISOLanguageName(
// name: "Hiri Motu", // name: "Hiri Motu",
@ -265,9 +265,9 @@ abstract class LanguageLocals {
// name: "Interlingua", // name: "Interlingua",
// nativeName: "Interlingua", // nativeName: "Interlingua",
// ), // ),
"id": const ISOLanguageName( 'id': const ISOLanguageName(
name: "Indonesian", name: 'Indonesian',
nativeName: "Bahasa Indonesia", nativeName: 'Bahasa Indonesia',
), ),
// "ie": const ISOLanguageName( // "ie": const ISOLanguageName(
// name: "Interlingue", // name: "Interlingue",
@ -293,17 +293,17 @@ abstract class LanguageLocals {
// name: "Icelandic", // name: "Icelandic",
// nativeName: "Íslenska", // nativeName: "Íslenska",
// ), // ),
"it": const ISOLanguageName( 'it': const ISOLanguageName(
name: "Italian", name: 'Italian',
nativeName: "Italiano", nativeName: 'Italiano',
), ),
// "iu": const ISOLanguageName( // "iu": const ISOLanguageName(
// name: "Inuktitut", // name: "Inuktitut",
// nativeName: "ᐃᓄᒃᑎᑐᑦ", // nativeName: "ᐃᓄᒃᑎᑐᑦ",
// ), // ),
"ja": const ISOLanguageName( 'ja': const ISOLanguageName(
name: "Japanese", name: 'Japanese',
nativeName: "日本語", nativeName: '日本語',
), ),
// "jv": const ISOLanguageName( // "jv": const ISOLanguageName(
// name: "Javanese", // name: "Javanese",
@ -353,9 +353,9 @@ abstract class LanguageLocals {
// name: "Kongo", // name: "Kongo",
// nativeName: "KiKongo", // nativeName: "KiKongo",
// ), // ),
"ko": const ISOLanguageName( 'ko': const ISOLanguageName(
name: "Korean", name: 'Korean',
nativeName: "한국어 (韓國語), 조선말 (朝鮮語)", nativeName: '한국어 (韓國語), 조선말 (朝鮮語)',
), ),
// "ku": const ISOLanguageName( // "ku": const ISOLanguageName(
// name: "Kurdish", // name: "Kurdish",
@ -457,9 +457,9 @@ abstract class LanguageLocals {
// name: "North Ndebele", // name: "North Ndebele",
// nativeName: "isiNdebele", // nativeName: "isiNdebele",
// ), // ),
"ne": const ISOLanguageName( 'ne': const ISOLanguageName(
name: "Nepali", name: 'Nepali',
nativeName: "नेपाली", nativeName: 'नेपाली',
), ),
// "ng": const ISOLanguageName( // "ng": const ISOLanguageName(
// name: "Ndonga", // name: "Ndonga",
@ -513,21 +513,21 @@ abstract class LanguageLocals {
// name: "Pāli", // name: "Pāli",
// nativeName: "पाऴि", // nativeName: "पाऴि",
// ), // ),
"fa": const ISOLanguageName( 'fa': const ISOLanguageName(
name: "Persian", name: 'Persian',
nativeName: "فارسی", nativeName: 'فارسی',
), ),
"pl": const ISOLanguageName( 'pl': const ISOLanguageName(
name: "Polish", name: 'Polish',
nativeName: "polski", nativeName: 'polski',
), ),
// "ps": const ISOLanguageName( // "ps": const ISOLanguageName(
// name: "Pashto, Pushto", // name: "Pashto, Pushto",
// nativeName: "پښتو", // nativeName: "پښتو",
// ), // ),
"pt": const ISOLanguageName( 'pt': const ISOLanguageName(
name: "Portuguese", name: 'Portuguese',
nativeName: "Português", nativeName: 'Português',
), ),
// "qu": const ISOLanguageName( // "qu": const ISOLanguageName(
// name: "Quechua", // name: "Quechua",
@ -545,9 +545,9 @@ abstract class LanguageLocals {
// name: "Romanian, Moldavian, Moldovan", // name: "Romanian, Moldavian, Moldovan",
// nativeName: "română", // nativeName: "română",
// ), // ),
"ru": const ISOLanguageName( 'ru': const ISOLanguageName(
name: "Russian", name: 'Russian',
nativeName: "русский язык", nativeName: 'русский язык',
), ),
// "sa": const ISOLanguageName( // "sa": const ISOLanguageName(
// name: "Sanskrit (Saṁskṛta)", // name: "Sanskrit (Saṁskṛta)",
@ -605,9 +605,9 @@ abstract class LanguageLocals {
// name: "Southern Sotho", // name: "Southern Sotho",
// nativeName: "Sesotho", // nativeName: "Sesotho",
// ), // ),
"es": const ISOLanguageName( 'es': const ISOLanguageName(
name: "Spanish", name: 'Spanish',
nativeName: "español", nativeName: 'español',
), ),
// "su": const ISOLanguageName( // "su": const ISOLanguageName(
// name: "Sundanese", // name: "Sundanese",
@ -637,9 +637,9 @@ abstract class LanguageLocals {
// name: "Tajik", // name: "Tajik",
// nativeName: "тоҷикӣ, toğikī, تاجیکی‎", // nativeName: "тоҷикӣ, toğikī, تاجیکی‎",
// ), // ),
"th": const ISOLanguageName( 'th': const ISOLanguageName(
name: "Thai", name: 'Thai',
nativeName: "ไทย", nativeName: 'ไทย',
), ),
// "ti": const ISOLanguageName( // "ti": const ISOLanguageName(
// name: "Tigrinya", // name: "Tigrinya",
@ -665,9 +665,9 @@ abstract class LanguageLocals {
// name: "Tonga (Tonga Islands)", // name: "Tonga (Tonga Islands)",
// nativeName: "faka Tonga", // nativeName: "faka Tonga",
// ), // ),
"tr": const ISOLanguageName( 'tr': const ISOLanguageName(
name: "Turkish", name: 'Turkish',
nativeName: "Türkçe", nativeName: 'Türkçe',
), ),
// "ts": const ISOLanguageName( // "ts": const ISOLanguageName(
// name: "Tsonga", // name: "Tsonga",
@ -689,9 +689,9 @@ abstract class LanguageLocals {
// name: "Uighur, Uyghur", // name: "Uighur, Uyghur",
// nativeName: "Uyƣurqə, ئۇيغۇرچە‎", // nativeName: "Uyƣurqə, ئۇيغۇرچە‎",
// ), // ),
"uk": const ISOLanguageName( 'uk': const ISOLanguageName(
name: "Ukrainian", name: 'Ukrainian',
nativeName: "українська", nativeName: 'українська',
), ),
// "ur": const ISOLanguageName( // "ur": const ISOLanguageName(
// name: "Urdu", // name: "Urdu",
@ -705,9 +705,9 @@ abstract class LanguageLocals {
// name: "Venda", // name: "Venda",
// nativeName: "Tshivenḓa", // nativeName: "Tshivenḓa",
// ), // ),
"vi": const ISOLanguageName( 'vi': const ISOLanguageName(
name: "Vietnamese", name: 'Vietnamese',
nativeName: "Tiếng Việt", nativeName: 'Tiếng Việt',
), ),
// "vo": const ISOLanguageName( // "vo": const ISOLanguageName(
// name: "Volapük", // name: "Volapük",
@ -751,7 +751,7 @@ abstract class LanguageLocals {
if (isoLangs.containsKey(key)) { if (isoLangs.containsKey(key)) {
return isoLangs[key]!; return isoLangs[key]!;
} else { } else {
throw Exception("Language key incorrect"); throw Exception('Language key incorrect');
} }
} }
} }

View File

@ -3,187 +3,187 @@
import 'package:spotify/spotify.dart'; import 'package:spotify/spotify.dart';
final spotifyMarkets = [ final spotifyMarkets = [
(Market.AL, "Albania (AL)"), (Market.AL, 'Albania (AL)'),
(Market.DZ, "Algeria (DZ)"), (Market.DZ, 'Algeria (DZ)'),
(Market.AD, "Andorra (AD)"), (Market.AD, 'Andorra (AD)'),
(Market.AO, "Angola (AO)"), (Market.AO, 'Angola (AO)'),
(Market.AG, "Antigua and Barbuda (AG)"), (Market.AG, 'Antigua and Barbuda (AG)'),
(Market.AR, "Argentina (AR)"), (Market.AR, 'Argentina (AR)'),
(Market.AM, "Armenia (AM)"), (Market.AM, 'Armenia (AM)'),
(Market.AU, "Australia (AU)"), (Market.AU, 'Australia (AU)'),
(Market.AT, "Austria (AT)"), (Market.AT, 'Austria (AT)'),
(Market.AZ, "Azerbaijan (AZ)"), (Market.AZ, 'Azerbaijan (AZ)'),
(Market.BH, "Bahrain (BH)"), (Market.BH, 'Bahrain (BH)'),
(Market.BD, "Bangladesh (BD)"), (Market.BD, 'Bangladesh (BD)'),
(Market.BB, "Barbados (BB)"), (Market.BB, 'Barbados (BB)'),
(Market.BY, "Belarus (BY)"), (Market.BY, 'Belarus (BY)'),
(Market.BE, "Belgium (BE)"), (Market.BE, 'Belgium (BE)'),
(Market.BZ, "Belize (BZ)"), (Market.BZ, 'Belize (BZ)'),
(Market.BJ, "Benin (BJ)"), (Market.BJ, 'Benin (BJ)'),
(Market.BT, "Bhutan (BT)"), (Market.BT, 'Bhutan (BT)'),
(Market.BO, "Bolivia (BO)"), (Market.BO, 'Bolivia (BO)'),
(Market.BA, "Bosnia and Herzegovina (BA)"), (Market.BA, 'Bosnia and Herzegovina (BA)'),
(Market.BW, "Botswana (BW)"), (Market.BW, 'Botswana (BW)'),
(Market.BR, "Brazil (BR)"), (Market.BR, 'Brazil (BR)'),
(Market.BN, "Brunei Darussalam (BN)"), (Market.BN, 'Brunei Darussalam (BN)'),
(Market.BG, "Bulgaria (BG)"), (Market.BG, 'Bulgaria (BG)'),
(Market.BF, "Burkina Faso (BF)"), (Market.BF, 'Burkina Faso (BF)'),
(Market.BI, "Burundi (BI)"), (Market.BI, 'Burundi (BI)'),
(Market.CV, "Cabo Verde / Cape Verde (CV)"), (Market.CV, 'Cabo Verde / Cape Verde (CV)'),
(Market.KH, "Cambodia (KH)"), (Market.KH, 'Cambodia (KH)'),
(Market.CM, "Cameroon (CM)"), (Market.CM, 'Cameroon (CM)'),
(Market.CA, "Canada (CA)"), (Market.CA, 'Canada (CA)'),
(Market.TD, "Chad (TD)"), (Market.TD, 'Chad (TD)'),
(Market.CL, "Chile (CL)"), (Market.CL, 'Chile (CL)'),
(Market.CO, "Colombia (CO)"), (Market.CO, 'Colombia (CO)'),
(Market.KM, "Comoros (KM)"), (Market.KM, 'Comoros (KM)'),
(Market.CR, "Costa Rica (CR)"), (Market.CR, 'Costa Rica (CR)'),
(Market.HR, "Croatia (HR)"), (Market.HR, 'Croatia (HR)'),
(Market.CW, "Curaçao (CW)"), (Market.CW, 'Curaçao (CW)'),
(Market.CY, "Cyprus (CY)"), (Market.CY, 'Cyprus (CY)'),
(Market.CZ, "Czech Republic (CZ)"), (Market.CZ, 'Czech Republic (CZ)'),
(Market.CI, "Ivory Coast (CI)"), (Market.CI, 'Ivory Coast (CI)'),
(Market.CD, "Congo (CD)"), (Market.CD, 'Congo (CD)'),
(Market.DK, "Denmark (DK)"), (Market.DK, 'Denmark (DK)'),
(Market.DJ, "Djibouti (DJ)"), (Market.DJ, 'Djibouti (DJ)'),
(Market.DM, "Dominica (DM)"), (Market.DM, 'Dominica (DM)'),
(Market.DO, "Dominican Republic (DO)"), (Market.DO, 'Dominican Republic (DO)'),
(Market.EC, "Ecuador (EC)"), (Market.EC, 'Ecuador (EC)'),
(Market.EG, "Egypt (EG)"), (Market.EG, 'Egypt (EG)'),
(Market.SV, "El Salvador (SV)"), (Market.SV, 'El Salvador (SV)'),
(Market.GQ, "Equatorial Guinea (GQ)"), (Market.GQ, 'Equatorial Guinea (GQ)'),
(Market.EE, "Estonia (EE)"), (Market.EE, 'Estonia (EE)'),
(Market.SZ, "Eswatini (SZ)"), (Market.SZ, 'Eswatini (SZ)'),
(Market.FJ, "Fiji (FJ)"), (Market.FJ, 'Fiji (FJ)'),
(Market.FI, "Finland (FI)"), (Market.FI, 'Finland (FI)'),
(Market.FR, "France (FR)"), (Market.FR, 'France (FR)'),
(Market.GA, "Gabon (GA)"), (Market.GA, 'Gabon (GA)'),
(Market.GE, "Georgia (GE)"), (Market.GE, 'Georgia (GE)'),
(Market.DE, "Germany (DE)"), (Market.DE, 'Germany (DE)'),
(Market.GH, "Ghana (GH)"), (Market.GH, 'Ghana (GH)'),
(Market.GR, "Greece (GR)"), (Market.GR, 'Greece (GR)'),
(Market.GD, "Grenada (GD)"), (Market.GD, 'Grenada (GD)'),
(Market.GT, "Guatemala (GT)"), (Market.GT, 'Guatemala (GT)'),
(Market.GN, "Guinea (GN)"), (Market.GN, 'Guinea (GN)'),
(Market.GW, "Guinea-Bissau (GW)"), (Market.GW, 'Guinea-Bissau (GW)'),
(Market.GY, "Guyana (GY)"), (Market.GY, 'Guyana (GY)'),
(Market.HT, "Haiti (HT)"), (Market.HT, 'Haiti (HT)'),
(Market.HN, "Honduras (HN)"), (Market.HN, 'Honduras (HN)'),
(Market.HK, "Hong Kong (HK)"), (Market.HK, 'Hong Kong (HK)'),
(Market.HU, "Hungary (HU)"), (Market.HU, 'Hungary (HU)'),
(Market.IS, "Iceland (IS)"), (Market.IS, 'Iceland (IS)'),
(Market.IN, "India (IN)"), (Market.IN, 'India (IN)'),
(Market.ID, "Indonesia (ID)"), (Market.ID, 'Indonesia (ID)'),
(Market.IQ, "Iraq (IQ)"), (Market.IQ, 'Iraq (IQ)'),
(Market.IE, "Ireland (IE)"), (Market.IE, 'Ireland (IE)'),
(Market.IL, "Israel (IL)"), (Market.IL, 'Israel (IL)'),
(Market.IT, "Italy (IT)"), (Market.IT, 'Italy (IT)'),
(Market.JM, "Jamaica (JM)"), (Market.JM, 'Jamaica (JM)'),
(Market.JP, "Japan (JP)"), (Market.JP, 'Japan (JP)'),
(Market.JO, "Jordan (JO)"), (Market.JO, 'Jordan (JO)'),
(Market.KZ, "Kazakhstan (KZ)"), (Market.KZ, 'Kazakhstan (KZ)'),
(Market.KE, "Kenya (KE)"), (Market.KE, 'Kenya (KE)'),
(Market.KI, "Kiribati (KI)"), (Market.KI, 'Kiribati (KI)'),
(Market.XK, "Kosovo (XK)"), (Market.XK, 'Kosovo (XK)'),
(Market.KW, "Kuwait (KW)"), (Market.KW, 'Kuwait (KW)'),
(Market.KG, "Kyrgyzstan (KG)"), (Market.KG, 'Kyrgyzstan (KG)'),
(Market.LA, "Laos (LA)"), (Market.LA, 'Laos (LA)'),
(Market.LV, "Latvia (LV)"), (Market.LV, 'Latvia (LV)'),
(Market.LB, "Lebanon (LB)"), (Market.LB, 'Lebanon (LB)'),
(Market.LS, "Lesotho (LS)"), (Market.LS, 'Lesotho (LS)'),
(Market.LR, "Liberia (LR)"), (Market.LR, 'Liberia (LR)'),
(Market.LY, "Libya (LY)"), (Market.LY, 'Libya (LY)'),
(Market.LI, "Liechtenstein (LI)"), (Market.LI, 'Liechtenstein (LI)'),
(Market.LT, "Lithuania (LT)"), (Market.LT, 'Lithuania (LT)'),
(Market.LU, "Luxembourg (LU)"), (Market.LU, 'Luxembourg (LU)'),
(Market.MO, "Macao / Macau (MO)"), (Market.MO, 'Macao / Macau (MO)'),
(Market.MG, "Madagascar (MG)"), (Market.MG, 'Madagascar (MG)'),
(Market.MW, "Malawi (MW)"), (Market.MW, 'Malawi (MW)'),
(Market.MY, "Malaysia (MY)"), (Market.MY, 'Malaysia (MY)'),
(Market.MV, "Maldives (MV)"), (Market.MV, 'Maldives (MV)'),
(Market.ML, "Mali (ML)"), (Market.ML, 'Mali (ML)'),
(Market.MT, "Malta (MT)"), (Market.MT, 'Malta (MT)'),
(Market.MH, "Marshall Islands (MH)"), (Market.MH, 'Marshall Islands (MH)'),
(Market.MR, "Mauritania (MR)"), (Market.MR, 'Mauritania (MR)'),
(Market.MU, "Mauritius (MU)"), (Market.MU, 'Mauritius (MU)'),
(Market.MX, "Mexico (MX)"), (Market.MX, 'Mexico (MX)'),
(Market.FM, "Micronesia (FM)"), (Market.FM, 'Micronesia (FM)'),
(Market.MD, "Moldova (MD)"), (Market.MD, 'Moldova (MD)'),
(Market.MC, "Monaco (MC)"), (Market.MC, 'Monaco (MC)'),
(Market.MN, "Mongolia (MN)"), (Market.MN, 'Mongolia (MN)'),
(Market.ME, "Montenegro (ME)"), (Market.ME, 'Montenegro (ME)'),
(Market.MA, "Morocco (MA)"), (Market.MA, 'Morocco (MA)'),
(Market.MZ, "Mozambique (MZ)"), (Market.MZ, 'Mozambique (MZ)'),
(Market.NA, "Namibia (NA)"), (Market.NA, 'Namibia (NA)'),
(Market.NR, "Nauru (NR)"), (Market.NR, 'Nauru (NR)'),
(Market.NP, "Nepal (NP)"), (Market.NP, 'Nepal (NP)'),
(Market.NL, "Netherlands (NL)"), (Market.NL, 'Netherlands (NL)'),
(Market.NZ, "New Zealand (NZ)"), (Market.NZ, 'New Zealand (NZ)'),
(Market.NI, "Nicaragua (NI)"), (Market.NI, 'Nicaragua (NI)'),
(Market.NE, "Niger (NE)"), (Market.NE, 'Niger (NE)'),
(Market.NG, "Nigeria (NG)"), (Market.NG, 'Nigeria (NG)'),
(Market.MK, "North Macedonia (MK)"), (Market.MK, 'North Macedonia (MK)'),
(Market.NO, "Norway (NO)"), (Market.NO, 'Norway (NO)'),
(Market.OM, "Oman (OM)"), (Market.OM, 'Oman (OM)'),
(Market.PK, "Pakistan (PK)"), (Market.PK, 'Pakistan (PK)'),
(Market.PW, "Palau (PW)"), (Market.PW, 'Palau (PW)'),
(Market.PS, "Palestine (PS)"), (Market.PS, 'Palestine (PS)'),
(Market.PA, "Panama (PA)"), (Market.PA, 'Panama (PA)'),
(Market.PG, "Papua New Guinea (PG)"), (Market.PG, 'Papua New Guinea (PG)'),
(Market.PY, "Paraguay (PY)"), (Market.PY, 'Paraguay (PY)'),
(Market.PE, "Peru (PE)"), (Market.PE, 'Peru (PE)'),
(Market.PH, "Philippines (PH)"), (Market.PH, 'Philippines (PH)'),
(Market.PL, "Poland (PL)"), (Market.PL, 'Poland (PL)'),
(Market.PT, "Portugal (PT)"), (Market.PT, 'Portugal (PT)'),
(Market.QA, "Qatar (QA)"), (Market.QA, 'Qatar (QA)'),
(Market.CG, "Congo (CG)"), (Market.CG, 'Congo (CG)'),
(Market.RO, "Romania (RO)"), (Market.RO, 'Romania (RO)'),
(Market.RU, "Russia (RU)"), (Market.RU, 'Russia (RU)'),
(Market.RW, "Rwanda (RW)"), (Market.RW, 'Rwanda (RW)'),
(Market.WS, "Samoa (WS)"), (Market.WS, 'Samoa (WS)'),
(Market.SM, "San Marino (SM)"), (Market.SM, 'San Marino (SM)'),
(Market.SA, "Saudi Arabia (SA)"), (Market.SA, 'Saudi Arabia (SA)'),
(Market.SN, "Senegal (SN)"), (Market.SN, 'Senegal (SN)'),
(Market.RS, "Serbia (RS)"), (Market.RS, 'Serbia (RS)'),
(Market.SC, "Seychelles (SC)"), (Market.SC, 'Seychelles (SC)'),
(Market.SL, "Sierra Leone (SL)"), (Market.SL, 'Sierra Leone (SL)'),
(Market.SG, "Singapore (SG)"), (Market.SG, 'Singapore (SG)'),
(Market.SK, "Slovakia (SK)"), (Market.SK, 'Slovakia (SK)'),
(Market.SI, "Slovenia (SI)"), (Market.SI, 'Slovenia (SI)'),
(Market.SB, "Solomon Islands (SB)"), (Market.SB, 'Solomon Islands (SB)'),
(Market.ZA, "South Africa (ZA)"), (Market.ZA, 'South Africa (ZA)'),
(Market.KR, "South Korea (KR)"), (Market.KR, 'South Korea (KR)'),
(Market.ES, "Spain (ES)"), (Market.ES, 'Spain (ES)'),
(Market.LK, "Sri Lanka (LK)"), (Market.LK, 'Sri Lanka (LK)'),
(Market.KN, "St. Kitts and Nevis (KN)"), (Market.KN, 'St. Kitts and Nevis (KN)'),
(Market.LC, "St. Lucia (LC)"), (Market.LC, 'St. Lucia (LC)'),
(Market.SR, "Suriname (SR)"), (Market.SR, 'Suriname (SR)'),
(Market.SE, "Sweden (SE)"), (Market.SE, 'Sweden (SE)'),
(Market.CH, "Switzerland (CH)"), (Market.CH, 'Switzerland (CH)'),
(Market.ST, "São Tomé and Príncipe (ST)"), (Market.ST, 'São Tomé and Príncipe (ST)'),
(Market.TW, "Taiwan (TW)"), (Market.TW, 'Taiwan (TW)'),
(Market.TJ, "Tajikistan (TJ)"), (Market.TJ, 'Tajikistan (TJ)'),
(Market.TZ, "Tanzania (TZ)"), (Market.TZ, 'Tanzania (TZ)'),
(Market.TH, "Thailand (TH)"), (Market.TH, 'Thailand (TH)'),
(Market.BS, "The Bahamas (BS)"), (Market.BS, 'The Bahamas (BS)'),
(Market.GM, "The Gambia (GM)"), (Market.GM, 'The Gambia (GM)'),
(Market.TL, "East Timor (TL)"), (Market.TL, 'East Timor (TL)'),
(Market.TG, "Togo (TG)"), (Market.TG, 'Togo (TG)'),
(Market.TO, "Tonga (TO)"), (Market.TO, 'Tonga (TO)'),
(Market.TT, "Trinidad and Tobago (TT)"), (Market.TT, 'Trinidad and Tobago (TT)'),
(Market.TN, "Tunisia (TN)"), (Market.TN, 'Tunisia (TN)'),
(Market.TR, "Turkey (TR)"), (Market.TR, 'Turkey (TR)'),
(Market.TV, "Tuvalu (TV)"), (Market.TV, 'Tuvalu (TV)'),
(Market.UG, "Uganda (UG)"), (Market.UG, 'Uganda (UG)'),
(Market.UA, "Ukraine (UA)"), (Market.UA, 'Ukraine (UA)'),
(Market.AE, "United Arab Emirates (AE)"), (Market.AE, 'United Arab Emirates (AE)'),
(Market.GB, "United Kingdom (GB)"), (Market.GB, 'United Kingdom (GB)'),
(Market.US, "United States (US)"), (Market.US, 'United States (US)'),
(Market.UY, "Uruguay (UY)"), (Market.UY, 'Uruguay (UY)'),
(Market.UZ, "Uzbekistan (UZ)"), (Market.UZ, 'Uzbekistan (UZ)'),
(Market.VU, "Vanuatu (VU)"), (Market.VU, 'Vanuatu (VU)'),
(Market.VE, "Venezuela (VE)"), (Market.VE, 'Venezuela (VE)'),
(Market.VN, "Vietnam (VN)"), (Market.VN, 'Vietnam (VN)'),
(Market.ZM, "Zambia (ZM)"), (Market.ZM, 'Zambia (ZM)'),
(Market.ZW, "Zimbabwe (ZW)"), (Market.ZW, 'Zimbabwe (ZW)'),
]; ];

View File

@ -81,7 +81,7 @@ class AudioPlayerProvider extends GetxController {
} }
Future<AudioPlayerState?> _readSavedState() async { Future<AudioPlayerState?> _readSavedState() async {
final data = _prefs.getString("player_state"); final data = _prefs.getString('player_state');
if (data == null) return null; if (data == null) return null;
return AudioPlayerState.fromJson(jsonDecode(data)); return AudioPlayerState.fromJson(jsonDecode(data));
@ -89,7 +89,7 @@ class AudioPlayerProvider extends GetxController {
Future<void> _updateSavedState() async { Future<void> _updateSavedState() async {
final out = jsonEncode(state.value.toJson()); final out = jsonEncode(state.value.toJson());
await _prefs.setString("player_state", out); await _prefs.setString('player_state', out);
} }
Future<void> addCollections(List<String> collectionIds) async { Future<void> addCollections(List<String> collectionIds) async {

View File

@ -8,8 +8,8 @@ class SpotifyProvider extends GetxController {
void onInit() { void onInit() {
api = SpotifyApi( api = SpotifyApi(
SpotifyApiCredentials( SpotifyApiCredentials(
"f73d4bff91d64d89be9930036f553534", 'f73d4bff91d64d89be9930036f553534',
"5cbec0b928d247cd891d06195f07b8c9", '5cbec0b928d247cd891d06195f07b8c9',
), ),
); );
super.onInit(); super.onInit();

View File

@ -1,5 +1,6 @@
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
import 'package:rhythm_box/screens/explore.dart'; import 'package:rhythm_box/screens/explore.dart';
import 'package:rhythm_box/screens/player/view.dart';
import 'package:rhythm_box/screens/playlist/view.dart'; import 'package:rhythm_box/screens/playlist/view.dart';
import 'package:rhythm_box/screens/settings.dart'; import 'package:rhythm_box/screens/settings.dart';
import 'package:rhythm_box/shells/nav_shell.dart'; import 'package:rhythm_box/shells/nav_shell.dart';
@ -9,22 +10,27 @@ final router = GoRouter(routes: [
builder: (context, state, child) => NavShell(child: child), builder: (context, state, child) => NavShell(child: child),
routes: [ routes: [
GoRoute( GoRoute(
path: "/", path: '/',
name: "explore", name: 'explore',
builder: (context, state) => const ExploreScreen(), builder: (context, state) => const ExploreScreen(),
), ),
GoRoute( GoRoute(
path: "/playlist/:id", path: '/playlist/:id',
name: "playlistView", name: 'playlistView',
builder: (context, state) => PlaylistViewScreen( builder: (context, state) => PlaylistViewScreen(
playlistId: state.pathParameters['id']!, playlistId: state.pathParameters['id']!,
), ),
), ),
GoRoute( GoRoute(
path: "/settings", path: '/settings',
name: "settings", name: 'settings',
builder: (context, state) => const SettingsScreen(), builder: (context, state) => const SettingsScreen(),
), ),
], ],
), ),
GoRoute(
path: '/player',
name: 'player',
builder: (context, state) => const PlayerScreen(),
),
]); ]);

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
class PlayerScreen extends StatefulWidget {
const PlayerScreen({super.key});
@override
State<PlayerScreen> createState() => _PlayerScreenState();
}
class _PlayerScreenState extends State<PlayerScreen> {
@override
Widget build(BuildContext context) {
return const Placeholder();
}
}

View File

@ -104,7 +104,7 @@ class _PlaylistViewScreenState extends State<PlaylistViewScreen> {
"${NumberFormat.compactCurrency(symbol: '', decimalDigits: 2).format(_playlist!.followers!.total!)} saves", "${NumberFormat.compactCurrency(symbol: '', decimalDigits: 2).format(_playlist!.followers!.total!)} saves",
), ),
Text( Text(
"#${_playlist!.id}", '#${_playlist!.id}',
style: GoogleFonts.robotoMono(fontSize: 10), style: GoogleFonts.robotoMono(fontSize: 10),
), ),
], ],

View File

@ -2,12 +2,12 @@ import 'package:spotify/spotify.dart';
extension ArtistSimpleExtension on List<ArtistSimple> { extension ArtistSimpleExtension on List<ArtistSimple> {
String asString() { String asString() {
return map((e) => e.name?.replaceAll(",", " ")).join(", "); return map((e) => e.name?.replaceAll(',', ' ')).join(', ');
} }
} }
extension ArtistExtension on List<Artist> { extension ArtistExtension on List<Artist> {
String asString() { String asString() {
return map((e) => e.name?.replaceAll(",", " ")).join(", "); return map((e) => e.name?.replaceAll(',', ' ')).join(', ');
} }
} }

View File

@ -34,7 +34,7 @@ class RhythmMedia extends mk.Media {
: "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}", : "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}",
extras: { extras: {
...?extras, ...?extras,
"track": switch (track) { 'track': switch (track) {
LocalTrack() => track.toJson(), LocalTrack() => track.toJson(),
SourcedTrack() => track.toJson(), SourcedTrack() => track.toJson(),
_ => track.toJson(), _ => track.toJson(),
@ -50,14 +50,14 @@ class RhythmMedia extends mk.Media {
LocalTrack() => super.uri, LocalTrack() => super.uri,
_ => _ =>
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:" "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
"$serverPort/stream/${track.id}", '$serverPort/stream/${track.id}',
}; };
} }
factory RhythmMedia.fromMedia(mk.Media media) { factory RhythmMedia.fromMedia(mk.Media media) {
final track = media.uri.startsWith("http") final track = media.uri.startsWith('http')
? Track.fromJson(media.extras?["track"]) ? Track.fromJson(media.extras?['track'])
: LocalTrack.fromJson(media.extras?["track"]); : LocalTrack.fromJson(media.extras?['track']);
return RhythmMedia( return RhythmMedia(
track, track,
extras: media.extras, extras: media.extras,
@ -87,12 +87,12 @@ abstract class AudioPlayerInterface {
AudioPlayerInterface() AudioPlayerInterface()
: _mkPlayer = CustomPlayer( : _mkPlayer = CustomPlayer(
configuration: const mk.PlayerConfiguration( configuration: const mk.PlayerConfiguration(
title: "Rhythm", title: 'Rhythm',
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error, logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
log("[Playback] Error: $event"); log('[Playback] Error: $event');
}); });
} }

View File

@ -19,14 +19,14 @@ class CustomPlayer extends Player {
bool _shuffled; bool _shuffled;
int _androidAudioSessionId = 0; int _androidAudioSessionId = 0;
String _packageName = ""; String _packageName = '';
AndroidAudioManager? _androidAudioManager; AndroidAudioManager? _androidAudioManager;
CustomPlayer({super.configuration}) CustomPlayer({super.configuration})
: _playerStateStream = StreamController.broadcast(), : _playerStateStream = StreamController.broadcast(),
_shuffleStream = StreamController.broadcast(), _shuffleStream = StreamController.broadcast(),
_shuffled = false { _shuffled = false {
nativePlayer.setProperty("network-timeout", "120"); nativePlayer.setProperty('network-timeout', '120');
_subscriptions = [ _subscriptions = [
stream.buffering.listen((event) { stream.buffering.listen((event) {
@ -63,10 +63,10 @@ class CustomPlayer extends Player {
notifyAudioSessionUpdate(true); notifyAudioSessionUpdate(true);
await nativePlayer.setProperty( await nativePlayer.setProperty(
"audiotrack-session-id", 'audiotrack-session-id',
_androidAudioSessionId.toString(), _androidAudioSessionId.toString(),
); );
await nativePlayer.setProperty("ao", "audiotrack,opensles,"); await nativePlayer.setProperty('ao', 'audiotrack,opensles,');
}); });
} }
} }
@ -76,11 +76,11 @@ class CustomPlayer extends Player {
sendBroadcast( sendBroadcast(
BroadcastMessage( BroadcastMessage(
name: active name: active
? "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION" ? 'android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION'
: "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION", : 'android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION',
data: { data: {
"android.media.extra.AUDIO_SESSION": _androidAudioSessionId, 'android.media.extra.AUDIO_SESSION': _androidAudioSessionId,
"android.media.extra.PACKAGE_NAME": _packageName 'android.media.extra.PACKAGE_NAME': _packageName
}, },
), ),
); );

View File

@ -27,9 +27,9 @@ class AudioServices with WidgetsBindingObserver {
: 'dev.solsynth.rhythmBox', : 'dev.solsynth.rhythmBox',
androidNotificationChannelName: 'RhythmBox', androidNotificationChannelName: 'RhythmBox',
androidNotificationOngoing: false, androidNotificationOngoing: false,
androidNotificationIcon: "drawable/ic_launcher_monochrome", androidNotificationIcon: 'drawable/ic_launcher_monochrome',
androidStopForegroundOnPause: false, androidStopForegroundOnPause: false,
androidNotificationChannelDescription: "RhythmBox Music", androidNotificationChannelDescription: 'RhythmBox Music',
), ),
) )
: null; : null;
@ -42,9 +42,9 @@ class AudioServices with WidgetsBindingObserver {
await smtc?.addTrack(track); await smtc?.addTrack(track);
mobile?.addItem(MediaItem( mobile?.addItem(MediaItem(
id: track.id!, id: track.id!,
album: track.album?.name ?? "", album: track.album?.name ?? '',
title: track.name!, title: track.name!,
artist: (track.artists)?.asString() ?? "", artist: (track.artists)?.asString() ?? '',
duration: track is SourcedTrack duration: track is SourcedTrack
? track.sourceInfo.duration ? track.sourceInfo.duration
: Duration(milliseconds: track.durationMs ?? 0), : Duration(milliseconds: track.durationMs ?? 0),

View File

@ -81,9 +81,9 @@ class WindowsAudioService {
await smtc.updateMetadata( await smtc.updateMetadata(
MusicMetadata( MusicMetadata(
title: track.name!, title: track.name!,
albumArtist: track.artists?.firstOrNull?.name ?? "Unknown", albumArtist: track.artists?.firstOrNull?.name ?? 'Unknown',
artist: track.artists?.asString() ?? "Unknown", artist: track.artists?.asString() ?? 'Unknown',
album: track.album?.name ?? "Unknown", album: track.album?.name ?? 'Unknown',
thumbnail: (track.album?.images).asUrlString(), thumbnail: (track.album?.images).asUrlString(),
), ),
); );

View File

@ -3,7 +3,7 @@ import 'package:uuid/uuid.dart';
abstract class PrimitiveUtils { abstract class PrimitiveUtils {
static bool containsTextInBracket(String matcher, String text) { static bool containsTextInBracket(String matcher, String text) {
final allMatches = RegExp(r"(?<=\().+?(?=\))").allMatches(matcher); final allMatches = RegExp(r'(?<=\().+?(?=\))').allMatches(matcher);
if (allMatches.isEmpty) return false; if (allMatches.isEmpty) return false;
return allMatches return allMatches
.map((e) => e.group(0)) .map((e) => e.group(0))
@ -19,13 +19,13 @@ abstract class PrimitiveUtils {
static String toReadableNumber(double num) { static String toReadableNumber(double num) {
if (num > 999 && num < 99999) { if (num > 999 && num < 99999) {
return "${(num / 1000).toStringAsFixed(0)}K"; return '${(num / 1000).toStringAsFixed(0)}K';
} else if (num > 99999 && num < 999999) { } else if (num > 99999 && num < 999999) {
return "${(num / 1000).toStringAsFixed(0)}K"; return '${(num / 1000).toStringAsFixed(0)}K';
} else if (num > 999999 && num < 999999999) { } else if (num > 999999 && num < 999999999) {
return "${(num / 1000000).toStringAsFixed(0)}M"; return '${(num / 1000000).toStringAsFixed(0)}M';
} else if (num > 999999999) { } else if (num > 999999999) {
return "${(num / 1000000000).toStringAsFixed(0)}B"; return '${(num / 1000000000).toStringAsFixed(0)}B';
} else { } else {
return num.toStringAsFixed(0); return num.toStringAsFixed(0);
} }

View File

@ -27,7 +27,7 @@ class RhythmMedia extends mk.Media {
: "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}", : "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:$serverPort/stream/${track.id}",
extras: { extras: {
...?extras, ...?extras,
"track": switch (track) { 'track': switch (track) {
LocalTrack() => track.toJson(), LocalTrack() => track.toJson(),
SourcedTrack() => track.toJson(), SourcedTrack() => track.toJson(),
_ => track.toJson(), _ => track.toJson(),
@ -43,14 +43,14 @@ class RhythmMedia extends mk.Media {
LocalTrack() => super.uri, LocalTrack() => super.uri,
_ => _ =>
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:" "http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
"$serverPort/stream/${track.id}", '$serverPort/stream/${track.id}',
}; };
} }
factory RhythmMedia.fromMedia(mk.Media media) { factory RhythmMedia.fromMedia(mk.Media media) {
final track = media.uri.startsWith("http") final track = media.uri.startsWith('http')
? Track.fromJson(media.extras?["track"]) ? Track.fromJson(media.extras?['track'])
: LocalTrack.fromJson(media.extras?["track"]); : LocalTrack.fromJson(media.extras?['track']);
return RhythmMedia( return RhythmMedia(
track, track,
extras: media.extras, extras: media.extras,
@ -80,12 +80,12 @@ abstract class AudioPlayerInterface {
AudioPlayerInterface() AudioPlayerInterface()
: _mkPlayer = CustomPlayer( : _mkPlayer = CustomPlayer(
configuration: const mk.PlayerConfiguration( configuration: const mk.PlayerConfiguration(
title: "Rhythm", title: 'Rhythm',
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error, logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
), ),
) { ) {
_mkPlayer.stream.error.listen((event) { _mkPlayer.stream.error.listen((event) {
log("[Playback] Error: $event"); log('[Playback] Error: $event');
}); });
} }

View File

@ -31,11 +31,11 @@ class ServerPlaybackRoutesProvider {
options: Options( options: Options(
headers: { headers: {
...request.headers, ...request.headers,
"User-Agent": 'User-Agent':
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36", 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
"host": Uri.parse(sourcedTrack.url).host, 'host': Uri.parse(sourcedTrack.url).host,
"Cache-Control": "max-age=0", 'Cache-Control': 'max-age=0',
"Connection": "keep-alive", 'Connection': 'keep-alive',
}, },
responseType: ResponseType.stream, responseType: ResponseType.stream,
validateStatus: (status) => status! < 500, validateStatus: (status) => status! < 500,
@ -54,7 +54,7 @@ class ServerPlaybackRoutesProvider {
res.statusCode!, res.statusCode!,
body: audioStream, body: audioStream,
context: { context: {
"shelf.io.buffer_output": false, 'shelf.io.buffer_output': false,
}, },
headers: res.headers.map, headers: res.headers.map,
); );

View File

@ -31,9 +31,9 @@ class PlaybackServerProvider extends GetxController {
RhythmMedia.serverPort = port; RhythmMedia.serverPort = port;
_router = Router(); _router = Router();
_router!.get("/ping", (Request request) => Response.ok("pong")); _router!.get('/ping', (Request request) => Response.ok('pong'));
_router!.get( _router!.get(
"/stream/<trackId>", '/stream/<trackId>',
Get.find<ServerPlaybackRoutesProvider>().getStreamTrackId, Get.find<ServerPlaybackRoutesProvider>().getStreamTrackId,
); );

View File

@ -17,29 +17,29 @@ abstract class SongLinkService {
try { try {
final client = GetConnect(); final client = GetConnect();
final res = await client.get( final res = await client.get(
"https://song.link/s/$spotifyId", 'https://song.link/s/$spotifyId',
headers: { headers: {
"Accept": 'Accept':
"text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8", 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
"User-Agent": 'User-Agent':
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36" 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36'
}, },
); );
final document = parse(res.body); final document = parse(res.body);
final script = document.getElementById("__NEXT_DATA__")?.text; final script = document.getElementById('__NEXT_DATA__')?.text;
if (script == null) { if (script == null) {
return <SongLink>[]; return <SongLink>[];
} }
final pageProps = jsonDecode(script) as Map<String, dynamic>; final pageProps = jsonDecode(script) as Map<String, dynamic>;
final songLinks = pageProps["props"]?["pageProps"]?["pageData"] final songLinks = pageProps['props']?['pageProps']?['pageData']
?["sections"] ?['sections']
?.firstWhere( ?.firstWhere(
(section) => section?["sectionId"] == "section|auto|links|listen", (section) => section?['sectionId'] == 'section|auto|links|listen',
)?["links"] as List?; )?['links'] as List?;
return songLinks?.map((link) => SongLink.fromJson(link)).toList() ?? return songLinks?.map((link) => SongLink.fromJson(link)).toList() ??
<SongLink>[]; <SongLink>[];

View File

@ -2,7 +2,7 @@ import 'package:rhythm_box/services/sourced_track/models/source_info.dart';
import 'package:rhythm_box/services/sourced_track/models/source_map.dart'; import 'package:rhythm_box/services/sourced_track/models/source_map.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");
final String label; final String label;

View File

@ -1,6 +1,6 @@
enum SearchMode { enum SearchMode {
youtube._("YouTube"), youtube._('YouTube'),
youtubeMusic._("YouTube Music"); youtubeMusic._('YouTube Music');
final String label; final String label;

View File

@ -87,7 +87,7 @@ class YoutubeVideoInfo {
dislikes: 0, dislikes: 0,
views: searchItem.views, views: searchItem.views,
channelName: searchItem.uploaderName, channelName: searchItem.uploaderName,
channelId: searchItem.uploaderUrl ?? "", channelId: searchItem.uploaderUrl ?? '',
publishedAt: searchItem.uploadedDate != null publishedAt: searchItem.uploadedDate != null
? DateTime.tryParse(searchItem.uploadedDate!) ?? DateTime(2003, 9, 9) ? DateTime.tryParse(searchItem.uploadedDate!) ?? DateTime(2003, 9, 9)
: DateTime(2003, 9, 9), : DateTime(2003, 9, 9),

View File

@ -41,18 +41,18 @@ abstract class SourcedTrack extends Track {
static SourcedTrack fromJson(Map<String, dynamic> json) { static SourcedTrack fromJson(Map<String, dynamic> json) {
// TODO Follow user preferences // TODO Follow user preferences
const audioSource = "youtube"; const audioSource = 'youtube';
final sourceInfo = SourceInfo.fromJson(json); final sourceInfo = SourceInfo.fromJson(json);
final source = SourceMap.fromJson(json); final source = SourceMap.fromJson(json);
final track = Track.fromJson(json); final track = Track.fromJson(json);
final siblings = (json["siblings"] as List) final siblings = (json['siblings'] as List)
.map((sibling) => SourceInfo.fromJson(sibling)) .map((sibling) => SourceInfo.fromJson(sibling))
.toList() .toList()
.cast<SourceInfo>(); .cast<SourceInfo>();
return switch (audioSource) { return switch (audioSource) {
"piped" => PipedSourcedTrack( 'piped' => PipedSourcedTrack(
source: source, source: source,
siblings: siblings, siblings: siblings,
sourceInfo: sourceInfo, sourceInfo: sourceInfo,
@ -87,11 +87,11 @@ abstract class SourcedTrack extends Track {
required Track track, required Track track,
}) async { }) async {
// TODO Follow user preferences // TODO Follow user preferences
const audioSource = "youtube"; const audioSource = 'youtube';
try { try {
return switch (audioSource) { return switch (audioSource) {
"piped" => await PipedSourcedTrack.fetchFromTrack(track: track), 'piped' => await PipedSourcedTrack.fetchFromTrack(track: track),
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track), _ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
}; };
} on TrackNotFoundError catch (_) { } on TrackNotFoundError catch (_) {
@ -111,10 +111,10 @@ abstract class SourcedTrack extends Track {
required Track track, required Track track,
}) { }) {
// TODO Follow user preferences // TODO Follow user preferences
const audioSource = "youtube"; const audioSource = 'youtube';
return switch (audioSource) { return switch (audioSource) {
"piped" => PipedSourcedTrack.fetchSiblings(track: track), 'piped' => PipedSourcedTrack.fetchSiblings(track: track),
_ => YoutubeSourcedTrack.fetchSiblings(track: track), _ => YoutubeSourcedTrack.fetchSiblings(track: track),
}; };
} }

View File

@ -97,8 +97,8 @@ class PipedSourcedTrack extends SourcedTrack {
info: PipedSourceInfo( info: PipedSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/${item.channelId}", artistUrl: 'https://www.youtube.com/${item.channelId}',
pageUrl: "https://www.youtube.com/watch?v=${item.id}", pageUrl: 'https://www.youtube.com/watch?v=${item.id}',
thumbnail: item.thumbnailUrl, thumbnail: item.thumbnailUrl,
title: item.title, title: item.title,
duration: item.duration, duration: item.duration,
@ -118,7 +118,7 @@ class PipedSourcedTrack extends SourcedTrack {
// TODO Allow user search with normal youtube video (`youtube`) // TODO Allow user search with normal youtube video (`youtube`)
const searchMode = SearchMode.youtubeMusic; const searchMode = SearchMode.youtubeMusic;
// TODO Follow user preferences // TODO Follow user preferences
const audioSource = "youtube"; const audioSource = 'youtube';
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
@ -131,7 +131,7 @@ class PipedSourcedTrack extends SourcedTrack {
// when falling back to piped API make sure to use the YouTube mode // when falling back to piped API make sure to use the YouTube mode
const isYouTubeMusic = const isYouTubeMusic =
audioSource != "piped" ? false : searchMode == SearchMode.youtubeMusic; audioSource != 'piped' ? false : searchMode == SearchMode.youtubeMusic;
if (isYouTubeMusic) { if (isYouTubeMusic) {
final artists = (track.artists ?? []) final artists = (track.artists ?? [])

View File

@ -15,7 +15,7 @@ import 'package:youtube_explode_dart/youtube_explode_dart.dart';
final youtubeClient = YoutubeExplode(); final youtubeClient = YoutubeExplode();
final officialMusicRegex = RegExp( final officialMusicRegex = RegExp(
r"official\s(video|audio|music\svideo|lyric\svideo|visualizer)", r'official\s(video|audio|music\svideo|lyric\svideo|visualizer)',
caseSensitive: false, caseSensitive: false,
); );
@ -62,11 +62,11 @@ class YoutubeSourcedTrack extends SourcedTrack {
static SourceMap toSourceMap(StreamManifest manifest) { static SourceMap toSourceMap(StreamManifest manifest) {
var m4a = manifest.audioOnly var m4a = manifest.audioOnly
.where((audio) => audio.codec.mimeType == "audio/mp4") .where((audio) => audio.codec.mimeType == 'audio/mp4')
.sortByBitrate(); .sortByBitrate();
var weba = manifest.audioOnly var weba = manifest.audioOnly
.where((audio) => audio.codec.mimeType == "audio/webm") .where((audio) => audio.codec.mimeType == 'audio/webm')
.sortByBitrate(); .sortByBitrate();
m4a = m4a.isEmpty ? weba.toList() : m4a; m4a = m4a.isEmpty ? weba.toList() : m4a;
@ -96,7 +96,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final manifest = final manifest =
await youtubeClient.videos.streamsClient.getManifest(item.id).timeout( await youtubeClient.videos.streamsClient.getManifest(item.id).timeout(
const Duration(seconds: 5), const Duration(seconds: 5),
onTimeout: () => throw ClientException("Timeout"), onTimeout: () => throw ClientException('Timeout'),
); );
sourceMap = toSourceMap(manifest); sourceMap = toSourceMap(manifest);
} }
@ -105,8 +105,8 @@ class YoutubeSourcedTrack extends SourcedTrack {
info: YoutubeSourceInfo( info: YoutubeSourceInfo(
id: item.id, id: item.id,
artist: item.channelName, artist: item.channelName,
artistUrl: "https://www.youtube.com/channel/${item.channelId}", artistUrl: 'https://www.youtube.com/channel/${item.channelId}',
pageUrl: "https://www.youtube.com/watch?v=${item.id}", pageUrl: 'https://www.youtube.com/watch?v=${item.id}',
thumbnail: item.thumbnailUrl, thumbnail: item.thumbnailUrl,
title: item.title, title: item.title,
duration: item.duration, duration: item.duration,
@ -179,7 +179,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
required Track track, required Track track,
}) async { }) async {
final links = await SongLinkService.links(track.id!); final links = await SongLinkService.links(track.id!);
final ytLink = links.firstWhereOrNull((link) => link.platform == "youtube"); final ytLink = links.firstWhereOrNull((link) => link.platform == 'youtube');
if (ytLink?.url != null if (ytLink?.url != null
// allows to fetch siblings more results for already sourced track // allows to fetch siblings more results for already sourced track
@ -203,7 +203,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
final query = SourcedTrack.getSearchTerm(track); final query = SourcedTrack.getSearchTerm(track);
final searchResults = await youtubeClient.search.search( final searchResults = await youtubeClient.search.search(
"$query - Topic", '$query - Topic',
filter: TypeFilters.video, filter: TypeFilters.video,
); );
@ -240,7 +240,7 @@ class YoutubeSourcedTrack extends SourcedTrack {
.getManifest(newSourceInfo.id) .getManifest(newSourceInfo.id)
.timeout( .timeout(
const Duration(seconds: 5), const Duration(seconds: 5),
onTimeout: () => throw ClientException("Timeout"), onTimeout: () => throw ClientException('Timeout'),
); );
// TODO Save to cache here // TODO Save to cache here

View File

@ -20,7 +20,7 @@ abstract class ServiceUtils {
static String clearArtistsOfTitle(String title, List<String> artists) { static String clearArtistsOfTitle(String title, List<String> artists) {
return title return title
.replaceAll(RegExp(artists.join("|"), caseSensitive: false), "") .replaceAll(RegExp(artists.join('|'), caseSensitive: false), '')
.trim(); .trim();
} }
@ -29,13 +29,13 @@ abstract class ServiceUtils {
List<String> artists = const [], List<String> artists = const [],
bool onlyCleanArtist = false, bool onlyCleanArtist = false,
}) { }) {
final match = RegExp(r"(?<=\().+?(?=\))").firstMatch(title)?.group(0); final match = RegExp(r'(?<=\().+?(?=\))').firstMatch(title)?.group(0);
final artistInBracket = final artistInBracket =
artists.any((artist) => match?.contains(artist) ?? false); artists.any((artist) => match?.contains(artist) ?? false);
if (artistInBracket) { if (artistInBracket) {
title = title.replaceAll( title = title.replaceAll(
RegExp(" *\\([^)]*\\) *"), RegExp(' *\\([^)]*\\) *'),
'', '',
); );
} }
@ -47,9 +47,9 @@ abstract class ServiceUtils {
return "$title ${artists.map((e) => e.replaceAll(",", " ")).join(", ")}" return "$title ${artists.map((e) => e.replaceAll(",", " ")).join(", ")}"
.toLowerCase() .toLowerCase()
.replaceAll(RegExp(r"\s*\[[^\]]*]"), ' ') .replaceAll(RegExp(r'\s*\[[^\]]*]'), ' ')
.replaceAll(RegExp(r"\sfeat\.|\sft\."), ' ') .replaceAll(RegExp(r'\sfeat\.|\sft\.'), ' ')
.replaceAll(RegExp(r"\s+"), ' ') .replaceAll(RegExp(r'\s+'), ' ')
.trim(); .trim();
} }
@ -60,18 +60,18 @@ abstract class ServiceUtils {
Document document = parser.parse(response.body); Document document = parser.parse(response.body);
String? lyrics = document.querySelector('div.lyrics')?.text.trim(); String? lyrics = document.querySelector('div.lyrics')?.text.trim();
if (lyrics == null) { if (lyrics == null) {
lyrics = ""; lyrics = '';
document document
.querySelectorAll("div[class^=\"Lyrics__Container\"]") .querySelectorAll('div[class^="Lyrics__Container"]')
.forEach((element) { .forEach((element) {
if (element.text.trim().isNotEmpty) { if (element.text.trim().isNotEmpty) {
final snippet = element.innerHtml.replaceAll("<br>", "\n").replaceAll( final snippet = element.innerHtml.replaceAll('<br>', '\n').replaceAll(
RegExp("<(?!\\s*br\\s*\\/?)[^>]+>", caseSensitive: false), RegExp('<(?!\\s*br\\s*\\/?)[^>]+>', caseSensitive: false),
"", '',
); );
final el = document.createElement("textarea"); final el = document.createElement('textarea');
el.innerHtml = snippet; el.innerHtml = snippet;
lyrics = "$lyrics${el.text.trim()}\n\n"; lyrics = '$lyrics${el.text.trim()}\n\n';
} }
}); });
} }
@ -143,16 +143,16 @@ abstract class ServiceUtils {
static DateTime parseSpotifyAlbumDate(AlbumSimple? album) { static DateTime parseSpotifyAlbumDate(AlbumSimple? album) {
if (album == null || album.releaseDate == null) { if (album == null || album.releaseDate == null) {
return DateTime.parse("1975-01-01"); return DateTime.parse('1975-01-01');
} }
switch (album.releaseDatePrecision ?? DatePrecision.year) { switch (album.releaseDatePrecision ?? DatePrecision.year) {
case DatePrecision.day: case DatePrecision.day:
return DateTime.parse(album.releaseDate!); return DateTime.parse(album.releaseDate!);
case DatePrecision.month: case DatePrecision.month:
return DateTime.parse("${album.releaseDate}-01"); return DateTime.parse('${album.releaseDate}-01');
case DatePrecision.year: case DatePrecision.year:
return DateTime.parse("${album.releaseDate}-01-01"); return DateTime.parse('${album.releaseDate}-01-01');
} }
} }
@ -162,9 +162,9 @@ abstract class ServiceUtils {
..sort((a, b) { ..sort((a, b) {
switch (sortBy) { switch (sortBy) {
case SortBy.ascending: case SortBy.ascending:
return a.name?.compareTo(b.name ?? "") ?? 0; return a.name?.compareTo(b.name ?? '') ?? 0;
case SortBy.descending: case SortBy.descending:
return b.name?.compareTo(a.name ?? "") ?? 0; return b.name?.compareTo(a.name ?? '') ?? 0;
case SortBy.newest: case SortBy.newest:
final aDate = parseSpotifyAlbumDate(a.album); final aDate = parseSpotifyAlbumDate(a.album);
final bDate = parseSpotifyAlbumDate(b.album); final bDate = parseSpotifyAlbumDate(b.album);
@ -177,10 +177,10 @@ abstract class ServiceUtils {
return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0; return a.durationMs?.compareTo(b.durationMs ?? 0) ?? 0;
case SortBy.artist: case SortBy.artist:
return a.artists?.first.name return a.artists?.first.name
?.compareTo(b.artists?.first.name ?? "") ?? ?.compareTo(b.artists?.first.name ?? '') ??
0; 0;
case SortBy.album: case SortBy.album:
return a.album?.name?.compareTo(b.album?.name ?? "") ?? 0; return a.album?.name?.compareTo(b.album?.name ?? '') ?? 0;
default: default:
return 0; return 0;
} }

View File

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:gap/gap.dart'; import 'package:gap/gap.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import 'package:rhythm_box/providers/audio_player.dart'; import 'package:rhythm_box/providers/audio_player.dart';
import 'package:rhythm_box/services/audio_player/audio_player.dart'; import 'package:rhythm_box/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/audio_services/image.dart'; import 'package:rhythm_box/services/audio_services/image.dart';
@ -102,7 +103,8 @@ class _BottomPlayerState extends State<BottomPlayer>
axis: Axis.vertical, axis: Axis.vertical,
axisAlignment: -1, axisAlignment: -1,
child: Obx( child: Obx(
() => Column( () => GestureDetector(
child: Column(
children: [ children: [
if (_durationCurrent != Duration.zero) if (_durationCurrent != Duration.zero)
TweenAnimationBuilder<double>( TweenAnimationBuilder<double>(
@ -122,6 +124,8 @@ class _BottomPlayerState extends State<BottomPlayer>
children: [ children: [
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Hero(
tag: const Key('current-active-track-album-art'),
child: _albumArt != null child: _albumArt != null
? AutoCacheImage(_albumArt!, width: 64, height: 64) ? AutoCacheImage(_albumArt!, width: 64, height: 64)
: Container( : Container(
@ -133,6 +137,7 @@ class _BottomPlayerState extends State<BottomPlayer>
child: const Center(child: Icon(Icons.image)), child: const Center(child: Icon(Icons.image)),
), ),
), ),
),
const Gap(12), const Gap(12),
Expanded( Expanded(
child: PlayerTrackDetails( child: PlayerTrackDetails(
@ -166,6 +171,10 @@ class _BottomPlayerState extends State<BottomPlayer>
).paddingSymmetric(horizontal: 12, vertical: 8), ).paddingSymmetric(horizontal: 12, vertical: 8),
], ],
), ),
onTap: () {
GoRouter.of(context).pushNamed('player');
},
),
), ),
); );
} }

View File

@ -23,7 +23,7 @@ class PlayerTrackDetails extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
InkWell( InkWell(
child: Text( child: Text(
playback.state.value.activeTrack?.name ?? "Not playing", playback.state.value.activeTrack?.name ?? 'Not playing',
maxLines: 1, maxLines: 1,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium!.copyWith( style: theme.textTheme.bodyMedium!.copyWith(
@ -36,7 +36,7 @@ class PlayerTrackDetails extends StatelessWidget {
), ),
Text( Text(
playback.state.value.activeTrack?.artists?.asString() ?? playback.state.value.activeTrack?.artists?.asString() ??
"No author", 'No author',
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall!.copyWith(color: color), style: theme.textTheme.bodySmall!.copyWith(color: color),
) )

View File

@ -377,7 +377,7 @@ packages:
source: hosted source: hosted
version: "0.6.7" version: "0.6.7"
json_annotation: json_annotation:
dependency: transitive dependency: "direct main"
description: description:
name: json_annotation name: json_annotation
sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"

View File

@ -65,6 +65,7 @@ dependencies:
shelf: ^1.4.1 shelf: ^1.4.1
shelf_router: ^1.1.4 shelf_router: ^1.1.4
dio: ^5.6.0 dio: ^5.6.0
json_annotation: ^4.9.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: