🎨 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
# producing the lint.
rules:
prefer_single_quotes: true
# avoid_print: false # Uncomment to disable the `avoid_print` 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 usdFormatter = NumberFormat.compactCurrency(
locale: 'en-US',
symbol: r"$",
symbol: r'$',
decimalDigits: 2,
);

View File

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

View File

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

View File

@ -81,7 +81,7 @@ class AudioPlayerProvider extends GetxController {
}
Future<AudioPlayerState?> _readSavedState() async {
final data = _prefs.getString("player_state");
final data = _prefs.getString('player_state');
if (data == null) return null;
return AudioPlayerState.fromJson(jsonDecode(data));
@ -89,7 +89,7 @@ class AudioPlayerProvider extends GetxController {
Future<void> _updateSavedState() async {
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 {

View File

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

View File

@ -1,5 +1,6 @@
import 'package:go_router/go_router.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/settings.dart';
import 'package:rhythm_box/shells/nav_shell.dart';
@ -9,22 +10,27 @@ final router = GoRouter(routes: [
builder: (context, state, child) => NavShell(child: child),
routes: [
GoRoute(
path: "/",
name: "explore",
path: '/',
name: 'explore',
builder: (context, state) => const ExploreScreen(),
),
GoRoute(
path: "/playlist/:id",
name: "playlistView",
path: '/playlist/:id',
name: 'playlistView',
builder: (context, state) => PlaylistViewScreen(
playlistId: state.pathParameters['id']!,
),
),
GoRoute(
path: "/settings",
name: "settings",
path: '/settings',
name: 'settings',
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",
),
Text(
"#${_playlist!.id}",
'#${_playlist!.id}',
style: GoogleFonts.robotoMono(fontSize: 10),
),
],

View File

@ -2,12 +2,12 @@ import 'package:spotify/spotify.dart';
extension ArtistSimpleExtension on List<ArtistSimple> {
String asString() {
return map((e) => e.name?.replaceAll(",", " ")).join(", ");
return map((e) => e.name?.replaceAll(',', ' ')).join(', ');
}
}
extension ArtistExtension on List<Artist> {
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}",
extras: {
...?extras,
"track": switch (track) {
'track': switch (track) {
LocalTrack() => track.toJson(),
SourcedTrack() => track.toJson(),
_ => track.toJson(),
@ -50,14 +50,14 @@ class RhythmMedia extends mk.Media {
LocalTrack() => super.uri,
_ =>
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
"$serverPort/stream/${track.id}",
'$serverPort/stream/${track.id}',
};
}
factory RhythmMedia.fromMedia(mk.Media media) {
final track = media.uri.startsWith("http")
? Track.fromJson(media.extras?["track"])
: LocalTrack.fromJson(media.extras?["track"]);
final track = media.uri.startsWith('http')
? Track.fromJson(media.extras?['track'])
: LocalTrack.fromJson(media.extras?['track']);
return RhythmMedia(
track,
extras: media.extras,
@ -87,12 +87,12 @@ abstract class AudioPlayerInterface {
AudioPlayerInterface()
: _mkPlayer = CustomPlayer(
configuration: const mk.PlayerConfiguration(
title: "Rhythm",
title: 'Rhythm',
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
),
) {
_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;
int _androidAudioSessionId = 0;
String _packageName = "";
String _packageName = '';
AndroidAudioManager? _androidAudioManager;
CustomPlayer({super.configuration})
: _playerStateStream = StreamController.broadcast(),
_shuffleStream = StreamController.broadcast(),
_shuffled = false {
nativePlayer.setProperty("network-timeout", "120");
nativePlayer.setProperty('network-timeout', '120');
_subscriptions = [
stream.buffering.listen((event) {
@ -63,10 +63,10 @@ class CustomPlayer extends Player {
notifyAudioSessionUpdate(true);
await nativePlayer.setProperty(
"audiotrack-session-id",
'audiotrack-session-id',
_androidAudioSessionId.toString(),
);
await nativePlayer.setProperty("ao", "audiotrack,opensles,");
await nativePlayer.setProperty('ao', 'audiotrack,opensles,');
});
}
}
@ -76,11 +76,11 @@ class CustomPlayer extends Player {
sendBroadcast(
BroadcastMessage(
name: active
? "android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION"
: "android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION",
? 'android.media.action.OPEN_AUDIO_EFFECT_CONTROL_SESSION'
: 'android.media.action.CLOSE_AUDIO_EFFECT_CONTROL_SESSION',
data: {
"android.media.extra.AUDIO_SESSION": _androidAudioSessionId,
"android.media.extra.PACKAGE_NAME": _packageName
'android.media.extra.AUDIO_SESSION': _androidAudioSessionId,
'android.media.extra.PACKAGE_NAME': _packageName
},
),
);

View File

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

View File

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

View File

@ -3,7 +3,7 @@ import 'package:uuid/uuid.dart';
abstract class PrimitiveUtils {
static bool containsTextInBracket(String matcher, String text) {
final allMatches = RegExp(r"(?<=\().+?(?=\))").allMatches(matcher);
final allMatches = RegExp(r'(?<=\().+?(?=\))').allMatches(matcher);
if (allMatches.isEmpty) return false;
return allMatches
.map((e) => e.group(0))
@ -19,13 +19,13 @@ abstract class PrimitiveUtils {
static String toReadableNumber(double num) {
if (num > 999 && num < 99999) {
return "${(num / 1000).toStringAsFixed(0)}K";
return '${(num / 1000).toStringAsFixed(0)}K';
} else if (num > 99999 && num < 999999) {
return "${(num / 1000).toStringAsFixed(0)}K";
return '${(num / 1000).toStringAsFixed(0)}K';
} else if (num > 999999 && num < 999999999) {
return "${(num / 1000000).toStringAsFixed(0)}M";
return '${(num / 1000000).toStringAsFixed(0)}M';
} else if (num > 999999999) {
return "${(num / 1000000000).toStringAsFixed(0)}B";
return '${(num / 1000000000).toStringAsFixed(0)}B';
} else {
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}",
extras: {
...?extras,
"track": switch (track) {
'track': switch (track) {
LocalTrack() => track.toJson(),
SourcedTrack() => track.toJson(),
_ => track.toJson(),
@ -43,14 +43,14 @@ class RhythmMedia extends mk.Media {
LocalTrack() => super.uri,
_ =>
"http://${PlatformInfo.isWindows ? "localhost" : InternetAddress.anyIPv4.address}:"
"$serverPort/stream/${track.id}",
'$serverPort/stream/${track.id}',
};
}
factory RhythmMedia.fromMedia(mk.Media media) {
final track = media.uri.startsWith("http")
? Track.fromJson(media.extras?["track"])
: LocalTrack.fromJson(media.extras?["track"]);
final track = media.uri.startsWith('http')
? Track.fromJson(media.extras?['track'])
: LocalTrack.fromJson(media.extras?['track']);
return RhythmMedia(
track,
extras: media.extras,
@ -80,12 +80,12 @@ abstract class AudioPlayerInterface {
AudioPlayerInterface()
: _mkPlayer = CustomPlayer(
configuration: const mk.PlayerConfiguration(
title: "Rhythm",
title: 'Rhythm',
logLevel: kDebugMode ? mk.MPVLogLevel.info : mk.MPVLogLevel.error,
),
) {
_mkPlayer.stream.error.listen((event) {
log("[Playback] Error: $event");
log('[Playback] Error: $event');
});
}

View File

@ -31,11 +31,11 @@ class ServerPlaybackRoutesProvider {
options: Options(
headers: {
...request.headers,
"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",
"host": Uri.parse(sourcedTrack.url).host,
"Cache-Control": "max-age=0",
"Connection": "keep-alive",
'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',
'host': Uri.parse(sourcedTrack.url).host,
'Cache-Control': 'max-age=0',
'Connection': 'keep-alive',
},
responseType: ResponseType.stream,
validateStatus: (status) => status! < 500,
@ -54,7 +54,7 @@ class ServerPlaybackRoutesProvider {
res.statusCode!,
body: audioStream,
context: {
"shelf.io.buffer_output": false,
'shelf.io.buffer_output': false,
},
headers: res.headers.map,
);

View File

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

View File

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

View File

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

View File

@ -87,7 +87,7 @@ class YoutubeVideoInfo {
dislikes: 0,
views: searchItem.views,
channelName: searchItem.uploaderName,
channelId: searchItem.uploaderUrl ?? "",
channelId: searchItem.uploaderUrl ?? '',
publishedAt: searchItem.uploadedDate != null
? DateTime.tryParse(searchItem.uploadedDate!) ?? 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) {
// TODO Follow user preferences
const audioSource = "youtube";
const audioSource = 'youtube';
final sourceInfo = SourceInfo.fromJson(json);
final source = SourceMap.fromJson(json);
final track = Track.fromJson(json);
final siblings = (json["siblings"] as List)
final siblings = (json['siblings'] as List)
.map((sibling) => SourceInfo.fromJson(sibling))
.toList()
.cast<SourceInfo>();
return switch (audioSource) {
"piped" => PipedSourcedTrack(
'piped' => PipedSourcedTrack(
source: source,
siblings: siblings,
sourceInfo: sourceInfo,
@ -87,11 +87,11 @@ abstract class SourcedTrack extends Track {
required Track track,
}) async {
// TODO Follow user preferences
const audioSource = "youtube";
const audioSource = 'youtube';
try {
return switch (audioSource) {
"piped" => await PipedSourcedTrack.fetchFromTrack(track: track),
'piped' => await PipedSourcedTrack.fetchFromTrack(track: track),
_ => await YoutubeSourcedTrack.fetchFromTrack(track: track),
};
} on TrackNotFoundError catch (_) {
@ -111,10 +111,10 @@ abstract class SourcedTrack extends Track {
required Track track,
}) {
// TODO Follow user preferences
const audioSource = "youtube";
const audioSource = 'youtube';
return switch (audioSource) {
"piped" => PipedSourcedTrack.fetchSiblings(track: track),
'piped' => PipedSourcedTrack.fetchSiblings(track: track),
_ => YoutubeSourcedTrack.fetchSiblings(track: track),
};
}

View File

@ -97,8 +97,8 @@ class PipedSourcedTrack extends SourcedTrack {
info: PipedSourceInfo(
id: item.id,
artist: item.channelName,
artistUrl: "https://www.youtube.com/${item.channelId}",
pageUrl: "https://www.youtube.com/watch?v=${item.id}",
artistUrl: 'https://www.youtube.com/${item.channelId}',
pageUrl: 'https://www.youtube.com/watch?v=${item.id}',
thumbnail: item.thumbnailUrl,
title: item.title,
duration: item.duration,
@ -118,7 +118,7 @@ class PipedSourcedTrack extends SourcedTrack {
// TODO Allow user search with normal youtube video (`youtube`)
const searchMode = SearchMode.youtubeMusic;
// TODO Follow user preferences
const audioSource = "youtube";
const audioSource = 'youtube';
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
const isYouTubeMusic =
audioSource != "piped" ? false : searchMode == SearchMode.youtubeMusic;
audioSource != 'piped' ? false : searchMode == SearchMode.youtubeMusic;
if (isYouTubeMusic) {
final artists = (track.artists ?? [])

View File

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

View File

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

View File

@ -4,6 +4,7 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:gap/gap.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/services/audio_player/audio_player.dart';
import 'package:rhythm_box/services/audio_services/image.dart';
@ -102,69 +103,77 @@ class _BottomPlayerState extends State<BottomPlayer>
axis: Axis.vertical,
axisAlignment: -1,
child: Obx(
() => Column(
children: [
if (_durationCurrent != Duration.zero)
TweenAnimationBuilder<double>(
tween: Tween(
begin: 0,
end: _durationCurrent.inMilliseconds /
max(_durationTotal.inMilliseconds, 1),
),
duration: const Duration(milliseconds: 100),
builder: (context, value, _) => LinearProgressIndicator(
minHeight: 3,
value: value,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: _albumArt != null
? AutoCacheImage(_albumArt!, width: 64, height: 64)
: Container(
color: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
width: 64,
height: 64,
child: const Center(child: Icon(Icons.image)),
),
),
const Gap(12),
Expanded(
child: PlayerTrackDetails(
track: _playback.state.value.activeTrack,
() => GestureDetector(
child: Column(
children: [
if (_durationCurrent != Duration.zero)
TweenAnimationBuilder<double>(
tween: Tween(
begin: 0,
end: _durationCurrent.inMilliseconds /
max(_durationTotal.inMilliseconds, 1),
),
duration: const Duration(milliseconds: 100),
builder: (context, value, _) => LinearProgressIndicator(
minHeight: 3,
value: value,
),
),
const Gap(12),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: _isFetchingActiveTrack
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
),
)
: Icon(
!_isPlaying ? Icons.play_arrow : Icons.pause,
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Hero(
tag: const Key('current-active-track-album-art'),
child: _albumArt != null
? AutoCacheImage(_albumArt!, width: 64, height: 64)
: Container(
color: Theme.of(context)
.colorScheme
.surfaceContainerHigh,
width: 64,
height: 64,
child: const Center(child: Icon(Icons.image)),
),
onPressed:
_isFetchingActiveTrack ? null : _togglePlayState,
),
],
),
const Gap(12),
],
).paddingSymmetric(horizontal: 12, vertical: 8),
],
),
const Gap(12),
Expanded(
child: PlayerTrackDetails(
track: _playback.state.value.activeTrack,
),
),
const Gap(12),
Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.end,
children: [
IconButton(
icon: _isFetchingActiveTrack
? const SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 3,
),
)
: Icon(
!_isPlaying ? Icons.play_arrow : Icons.pause,
),
onPressed:
_isFetchingActiveTrack ? null : _togglePlayState,
),
],
),
const Gap(12),
],
).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),
InkWell(
child: Text(
playback.state.value.activeTrack?.name ?? "Not playing",
playback.state.value.activeTrack?.name ?? 'Not playing',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodyMedium!.copyWith(
@ -36,7 +36,7 @@ class PlayerTrackDetails extends StatelessWidget {
),
Text(
playback.state.value.activeTrack?.artists?.asString() ??
"No author",
'No author',
overflow: TextOverflow.ellipsis,
style: theme.textTheme.bodySmall!.copyWith(color: color),
)

View File

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

View File

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