feat: move Amazon Music to extension, fix Deezer download timeout

This commit is contained in:
zarzet
2026-03-08 04:15:28 +07:00
parent 4a61ffea8d
commit 7d5cb574c6
105 changed files with 285 additions and 1440 deletions
-1
View File
@@ -17,7 +17,6 @@ final _routerProvider = Provider<GoRouter>((ref) {
settingsProvider.select((s) => s.hasCompletedTutorial),
);
// Determine initial location based on app state
String initialLocation;
if (isFirstLaunch) {
initialLocation = '/setup';
+2 -2
View File
@@ -1,8 +1,8 @@
/// App version and info constants
/// Update version here only - all other files will reference this
class AppInfo {
static const String version = '3.7.1';
static const String buildNumber = '104';
static const String version = '3.7.2';
static const String buildNumber = '105';
static const String fullVersion = '$version+$buildNumber';
+3 -3
View File
@@ -763,7 +763,7 @@ abstract class AppLocalizations {
/// App description in header card
///
/// In en, this message translates to:
/// **'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.'**
/// **'Download Spotify tracks in lossless quality from Tidal and Qobuz.'**
String get aboutAppDescription;
/// Section header for artist albums
@@ -1576,7 +1576,7 @@ abstract class AppLocalizations {
/// **'If a track is not available on the first provider, the app will automatically try the next one.'**
String get providerPriorityInfo;
/// Label for built-in providers (Tidal/Qobuz/Amazon)
/// Label for built-in providers (Tidal/Qobuz)
///
/// In en, this message translates to:
/// **'Built-in'**
@@ -3271,7 +3271,7 @@ abstract class AppLocalizations {
/// Tutorial welcome tip 2
///
/// In en, this message translates to:
/// **'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music'**
/// **'Get FLAC quality audio from Tidal, Qobuz, or Deezer'**
String get tutorialWelcomeTip2;
/// Tutorial welcome tip 3
+2 -2
View File
@@ -365,7 +365,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get aboutAppDescription =>
'Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.';
'Lade Spotify-Titel in verlustfreier Qualität von Tidal und Qobuz herunter.';
@override
String get artistAlbums => 'Alben';
@@ -1826,7 +1826,7 @@ class AppLocalizationsDe extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'FLAC-Qualität von Tidal, Qobuz oder Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -356,7 +356,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsEn extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Get FLAC quality audio from Tidal, Qobuz, or Deezer';
@override
String get tutorialWelcomeTip3 =>
+4 -4
View File
@@ -356,7 +356,7 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsEs extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Get FLAC quality audio from Tidal, Qobuz, or Deezer';
@override
String get tutorialWelcomeTip3 =>
@@ -2705,7 +2705,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs {
@override
String get aboutAppDescription =>
'Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.';
'Descarga pistas de Spotify con calidad sin pérdida de Tidal y Qobuz.';
@override
String get artistAlbums => 'Álbumes';
@@ -4150,7 +4150,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Obtén audio en calidad FLAC de Tidal, Qobuz o Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -358,7 +358,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1811,7 +1811,7 @@ class AppLocalizationsFr extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Audio en qualité FLAC depuis Tidal, Qobuz ou Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -356,7 +356,7 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsHi extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Tidal, Qobuz, या Deezer से FLAC गुणवत्ता ऑडियो प्राप्त करें';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -359,7 +359,7 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get aboutAppDescription =>
'Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.';
'Unduh lagu Spotify dalam kualitas lossless dari Tidal dan Qobuz.';
@override
String get artistAlbums => 'Album';
@@ -1816,7 +1816,7 @@ class AppLocalizationsId extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -3
View File
@@ -352,7 +352,7 @@ class AppLocalizationsJa extends AppLocalizations {
@override
String get aboutAppDescription =>
'Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。';
'Tidal、Qobuz から Spotify のトラックをロスレス品質でダウンロードします。';
@override
String get artistAlbums => 'アルバム';
@@ -1795,8 +1795,7 @@ class AppLocalizationsJa extends AppLocalizations {
'Download music from Spotify, Deezer, or paste any supported URL';
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
String get tutorialWelcomeTip2 => 'Tidal、Qobuz、Deezer から FLAC 品質のオーディオを取得';
@override
String get tutorialWelcomeTip3 =>
+2 -3
View File
@@ -355,7 +355,7 @@ class AppLocalizationsKo extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1807,8 +1807,7 @@ class AppLocalizationsKo extends AppLocalizations {
'Download music from Spotify, Deezer, or paste any supported URL';
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
String get tutorialWelcomeTip2 => 'Tidal, Qobuz 또는 Deezer에서 FLAC 품질 오디오 받기';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -356,7 +356,7 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsNl extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Krijg FLAC-kwaliteit audio van Tidal, Qobuz of Deezer';
@override
String get tutorialWelcomeTip3 =>
+4 -4
View File
@@ -356,7 +356,7 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsPt extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Get FLAC quality audio from Tidal, Qobuz, or Deezer';
@override
String get tutorialWelcomeTip3 =>
@@ -2705,7 +2705,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt {
@override
String get aboutAppDescription =>
'Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.';
'Baixe faixas do Spotify em qualidade sem perdas do Tidal e Qobuz.';
@override
String get artistAlbums => 'Álbuns';
@@ -4147,7 +4147,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Obtenha áudio em qualidade FLAC do Tidal, Qobuz ou Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -3
View File
@@ -363,7 +363,7 @@ class AppLocalizationsRu extends AppLocalizations {
@override
String get aboutAppDescription =>
'Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.';
'Скачайте треки Spotify в Lossless качестве из Tidal и Qobuz.';
@override
String get artistAlbums => 'Альбомы';
@@ -1855,8 +1855,7 @@ class AppLocalizationsRu extends AppLocalizations {
'Скачивайте музыку из Spotify, Deezer, или вставьте любой поддерживаемый URL';
@override
String get tutorialWelcomeTip2 =>
'Скачайте FLAC с Tidal, Qobuz или Amazon Music';
String get tutorialWelcomeTip2 => 'Скачайте FLAC с Tidal, Qobuz или Deezer';
@override
String get tutorialWelcomeTip3 =>
+2 -2
View File
@@ -361,7 +361,7 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get aboutAppDescription =>
'Spotify şarkılarını Tidal, Qobuz ve Amazon Music\'den yüksek kalitede indir.';
'Spotify şarkılarını Tidal ve Qobuz\'den yüksek kalitede indir.';
@override
String get artistAlbums => 'Albümler';
@@ -1821,7 +1821,7 @@ class AppLocalizationsTr extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Tidal, Qobuz veya Deezer\'den FLAC kalitesinde ses alın';
@override
String get tutorialWelcomeTip3 =>
+6 -8
View File
@@ -356,7 +356,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -1809,7 +1809,7 @@ class AppLocalizationsZh extends AppLocalizations {
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
'Get FLAC quality audio from Tidal, Qobuz, or Deezer';
@override
String get tutorialWelcomeTip3 =>
@@ -2696,7 +2696,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -4124,8 +4124,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh {
'Download music from Spotify, Deezer, or paste any supported URL';
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
String get tutorialWelcomeTip2 => '从 Tidal、Qobuz 或 Deezer 获取 FLAC 品质音频';
@override
String get tutorialWelcomeTip3 =>
@@ -4808,7 +4807,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
@override
String get aboutAppDescription =>
'Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.';
'Download Spotify tracks in lossless quality from Tidal and Qobuz.';
@override
String get artistAlbums => 'Albums';
@@ -6236,8 +6235,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh {
'Download music from Spotify, Deezer, or paste any supported URL';
@override
String get tutorialWelcomeTip2 =>
'Get FLAC quality audio from Tidal, Qobuz, or Amazon Music';
String get tutorialWelcomeTip2 => '從 Tidal、Qobuz 或 Deezer 取得 FLAC 品質音訊';
@override
String get tutorialWelcomeTip3 =>
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Lade Spotify-Titel in verlustfreier Qualität von Tidal, Qobuz und Amazon Music herunter.",
"aboutAppDescription": "Lade Spotify-Titel in verlustfreier Qualität von Tidal und Qobuz herunter.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Integriert",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Erweiterung",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "FLAC-Qualität von Tidal, Qobuz oder Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1097,7 +1097,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2383,7 +2383,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+2 -2
View File
@@ -402,7 +402,7 @@
"@aboutDabMusicDesc": {
"description": "Credit for DAB Music API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1005,7 +1005,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal, Qobuz y Amazon Music.",
"aboutAppDescription": "Descarga pistas de Spotify con calidad sin pérdida de Tidal y Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Integrado",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extensión",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Obtén audio en calidad FLAC de Tidal, Qobuz o Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Audio en qualité FLAC depuis Tidal, Qobuz ou Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Tidal, Qobuz, या Deezer से FLAC गुणवत्ता ऑडियो प्राप्त करें",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal, Qobuz, dan Amazon Music.",
"aboutAppDescription": "Unduh lagu Spotify dalam kualitas lossless dari Tidal dan Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1097,7 +1097,7 @@
},
"providerBuiltIn": "Bawaan",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Ekstensi",
"@providerExtension": {
@@ -2383,7 +2383,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Dapatkan audio kualitas FLAC dari Tidal, Qobuz, atau Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Tidal、Qobuz、Amazon Music から Spotify のトラックをロスレス品質でダウンロードします。",
"aboutAppDescription": "Tidal、Qobuz から Spotify のトラックをロスレス品質でダウンロードします。",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "内蔵",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "拡張",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "TidalQobuz、Deezer から FLAC 品質のオーディオを取得",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Tidal, Qobuz 또는 Deezer에서 FLAC 품질 오디오 받기",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Krijg FLAC-kwaliteit audio van Tidal, Qobuz of Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+2 -2
View File
@@ -402,7 +402,7 @@
"@aboutDabMusicDesc": {
"description": "Credit for DAB Music API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1005,7 +1005,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal, Qobuz e Amazon Music.",
"aboutAppDescription": "Baixe faixas do Spotify em qualidade sem perdas do Tidal e Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Embutido",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extensão",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Obtenha áudio em qualidade FLAC do Tidal, Qobuz ou Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Скачайте треки Spotify в Lossless качестве из Tidal, Qobuz и Amazon Music.",
"aboutAppDescription": "Скачайте треки Spotify в Lossless качестве из Tidal и Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Встроенные",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Расширение",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Скачайте FLAC с Tidal, Qobuz или Amazon Music",
"tutorialWelcomeTip2": "Скачайте FLAC с Tidal, Qobuz или Deezer",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Spotify şarkılarını Tidal, Qobuz ve Amazon Music'den yüksek kalitede indir.",
"aboutAppDescription": "Spotify şarkılarını Tidal ve Qobuz'den yüksek kalitede indir.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Dahili",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Eklenti",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": "Tidal, Qobuz veya Deezer'den FLAC kalitesinde ses alın",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+2 -2
View File
@@ -402,7 +402,7 @@
"@aboutDabMusicDesc": {
"description": "Credit for DAB Music API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1005,7 +1005,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": " TidalQobuz 或 Deezer 获取 FLAC 品质音频",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
+3 -3
View File
@@ -450,7 +450,7 @@
"@aboutSpotiSaverDesc": {
"description": "Credit for SpotiSaver API"
},
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal, Qobuz, and Amazon Music.",
"aboutAppDescription": "Download Spotify tracks in lossless quality from Tidal and Qobuz.",
"@aboutAppDescription": {
"description": "App description in header card"
},
@@ -1089,7 +1089,7 @@
},
"providerBuiltIn": "Built-in",
"@providerBuiltIn": {
"description": "Label for built-in providers (Tidal/Qobuz/Amazon)"
"description": "Label for built-in providers (Tidal/Qobuz)"
},
"providerExtension": "Extension",
"@providerExtension": {
@@ -2358,7 +2358,7 @@
"@tutorialWelcomeTip1": {
"description": "Tutorial welcome tip 1"
},
"tutorialWelcomeTip2": "Get FLAC quality audio from Tidal, Qobuz, or Amazon Music",
"tutorialWelcomeTip2": " TidalQobuz 或 Deezer 取得 FLAC 品質音訊",
"@tutorialWelcomeTip2": {
"description": "Tutorial welcome tip 2"
},
-16
View File
@@ -55,17 +55,14 @@ class AppSettings {
final String
songLinkRegion; // SongLink userCountry region code used for platform lookup
// Local Library Settings
final bool localLibraryEnabled; // Enable local library scanning
final String localLibraryPath; // Path to scan for audio files
final bool
localLibraryShowDuplicates; // Show indicator when searching for existing tracks
// Tutorial/Onboarding
final bool
hasCompletedTutorial; // Track if user has completed the app tutorial
// Lyrics Provider Settings
final List<String>
lyricsProviders; // Ordered list of enabled lyrics provider IDs
final bool
@@ -77,7 +74,6 @@ class AppSettings {
final String
musixmatchLanguage; // Optional ISO language code for Musixmatch localized lyrics
// Version upgrade tracking
final String
lastSeenVersion; // Last app version the user has acknowledged (e.g. '3.7.0')
@@ -124,13 +120,10 @@ class AppSettings {
this.downloadNetworkMode = 'any',
this.networkCompatibilityMode = false,
this.songLinkRegion = 'US',
// Local Library defaults
this.localLibraryEnabled = false,
this.localLibraryPath = '',
this.localLibraryShowDuplicates = true,
// Tutorial default
this.hasCompletedTutorial = false,
// Lyrics providers default order
this.lyricsProviders = const [
'lrclib',
'spotify_api',
@@ -143,7 +136,6 @@ class AppSettings {
this.lyricsIncludeRomanizationNetease = false,
this.lyricsMultiPersonWordByWord = false,
this.musixmatchLanguage = '',
// Version upgrade tracking
this.lastSeenVersion = '',
});
@@ -191,19 +183,15 @@ class AppSettings {
String? downloadNetworkMode,
bool? networkCompatibilityMode,
String? songLinkRegion,
// Local Library
bool? localLibraryEnabled,
String? localLibraryPath,
bool? localLibraryShowDuplicates,
// Tutorial
bool? hasCompletedTutorial,
// Lyrics providers
List<String>? lyricsProviders,
bool? lyricsIncludeTranslationNetease,
bool? lyricsIncludeRomanizationNetease,
bool? lyricsMultiPersonWordByWord,
String? musixmatchLanguage,
// Version upgrade tracking
String? lastSeenVersion,
}) {
return AppSettings(
@@ -259,14 +247,11 @@ class AppSettings {
networkCompatibilityMode:
networkCompatibilityMode ?? this.networkCompatibilityMode,
songLinkRegion: songLinkRegion ?? this.songLinkRegion,
// Local Library
localLibraryEnabled: localLibraryEnabled ?? this.localLibraryEnabled,
localLibraryPath: localLibraryPath ?? this.localLibraryPath,
localLibraryShowDuplicates:
localLibraryShowDuplicates ?? this.localLibraryShowDuplicates,
// Tutorial
hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial,
// Lyrics providers
lyricsProviders: lyricsProviders ?? this.lyricsProviders,
lyricsIncludeTranslationNetease:
lyricsIncludeTranslationNetease ??
@@ -277,7 +262,6 @@ class AppSettings {
lyricsMultiPersonWordByWord:
lyricsMultiPersonWordByWord ?? this.lyricsMultiPersonWordByWord,
musixmatchLanguage: musixmatchLanguage ?? this.musixmatchLanguage,
// Version upgrade tracking
lastSeenVersion: lastSeenVersion ?? this.lastSeenVersion,
);
}
+8 -24
View File
@@ -592,10 +592,8 @@ class DownloadHistoryNotifier extends Notifier<DownloadHistoryState> {
return 0;
}
// Delete from database
final deletedCount = await _db.deleteByIds(orphanedIds);
// Update in-memory state
final orphanedSet = orphanedIds.toSet();
state = state.copyWith(
items: state.items
@@ -1596,18 +1594,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
String _determineOutputExt(String quality, String service) {
// YouTube provider - lossy only (Opus or MP3)
if (service.toLowerCase() == 'youtube') {
if (quality.toLowerCase().contains('mp3')) {
return '.mp3';
}
return '.opus';
}
// Amazon stream is delivered as MP4/M4A container (may contain FLAC audio),
// so SAF should keep .m4a before decrypt/convert pipeline.
if (service.toLowerCase() == 'amazon') {
return '.m4a';
}
if (service.toLowerCase() == 'tidal' && quality == 'HIGH') {
return '.m4a';
}
@@ -2903,7 +2895,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
failedCount: _failedInSession,
);
// Auto-export failed downloads if enabled
final settings = ref.read(settingsProvider);
if (settings.autoExportFailedDownloads && _failedInSession > 0) {
final exportPath = await exportFailedDownloads();
@@ -3206,7 +3197,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
!trackToDownload.id.startsWith('deezer:') &&
!trackToDownload.id.startsWith('extension:')) {
try {
// Extract clean Spotify ID (remove spotify: prefix if present)
String spotifyId = trackToDownload.id;
if (spotifyId.startsWith('spotify:track:')) {
spotifyId = spotifyId.split(':').last;
@@ -3508,9 +3498,8 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
if (!wasExisting &&
decryptionKey.isNotEmpty &&
filePath != null &&
actualService == 'amazon') {
_log.i('Amazon encrypted stream detected, decrypting via FFmpeg...');
filePath != null) {
_log.i('Encrypted stream detected, decrypting via FFmpeg...');
updateItemStatus(item.id, DownloadStatus.downloading, progress: 0.9);
if (effectiveSafMode && isContentUri(filePath)) {
@@ -3539,7 +3528,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
updateItemStatus(
item.id,
DownloadStatus.failed,
error: 'Failed to decrypt Amazon stream',
error: 'Failed to decrypt encrypted stream',
errorType: DownloadErrorType.unknown,
);
return;
@@ -3564,7 +3553,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
);
if (newUri == null) {
_log.e('Failed to write decrypted Amazon stream back to SAF');
_log.e('Failed to write decrypted stream back to SAF');
updateItemStatus(
item.id,
DownloadStatus.failed,
@@ -3579,7 +3568,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
filePath = newUri;
finalSafFileName = newFileName;
_log.i('Amazon SAF decryption completed');
_log.i('SAF decryption completed');
} finally {
try {
await File(tempPath).delete();
@@ -3601,7 +3590,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
updateItemStatus(
item.id,
DownloadStatus.failed,
error: 'Failed to decrypt Amazon stream',
error: 'Failed to decrypt encrypted stream',
errorType: DownloadErrorType.unknown,
);
try {
@@ -3610,7 +3599,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
return;
}
filePath = decryptedPath;
_log.i('Amazon local decryption completed');
_log.i('Local decryption completed');
}
}
@@ -3832,7 +3821,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
} else {
// Local file path flow (original)
if (quality == 'HIGH') {
final tidalHighFormat = settings.tidalHighFormat;
_log.i(
@@ -4049,10 +4037,9 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
!effectiveSafMode &&
isFlacFile &&
!wasExisting &&
actualService == 'amazon' &&
decryptionKey.isNotEmpty) {
_log.d(
'Local FLAC after Amazon decrypt detected, embedding metadata and cover...',
'Local FLAC after decrypt detected, embedding metadata and cover...',
);
try {
updateItemStatus(
@@ -4112,7 +4099,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final isContentUriPath = isContentUri(filePath);
if (isContentUriPath && effectiveSafMode) {
// SAF mode: copy to temp, embed, write back
final tempPath = await _copySafToTemp(filePath);
if (tempPath != null) {
try {
@@ -4133,7 +4119,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
copyright: backendCopyright,
);
}
// Write back to SAF
final ext = isMp3File ? '.mp3' : '.opus';
final newFileName = '${safBaseName ?? 'track'}$ext';
final newUri = await _writeTempToSaf(
@@ -4162,7 +4147,6 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
}
}
} else {
// Non-SAF mode: embed directly
try {
if (isMp3File) {
await _embedMetadataToMp3(
+2 -12
View File
@@ -757,7 +757,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
Future<void> loadProviderPriority() async {
try {
// Load from SharedPreferences first (persisted)
final prefs = await SharedPreferences.getInstance();
final savedJson = prefs.getString(_providerPriorityKey);
@@ -768,10 +767,8 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
priority = _sanitizeDownloadProviderPriority(priority);
_log.d('Loaded provider priority from prefs: $priority');
await prefs.setString(_providerPriorityKey, jsonEncode(priority));
// Sync to Go backend
await PlatformBridge.setProviderPriority(priority);
} else {
// Fallback to Go backend default
priority = await PlatformBridge.getProviderPriority();
priority = _sanitizeDownloadProviderPriority(priority);
await PlatformBridge.setProviderPriority(priority);
@@ -787,11 +784,9 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
Future<void> setProviderPriority(List<String> priority) async {
try {
final sanitized = _sanitizeDownloadProviderPriority(priority);
// Save to SharedPreferences for persistence
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_providerPriorityKey, jsonEncode(sanitized));
// Sync to Go backend
await PlatformBridge.setProviderPriority(sanitized);
state = state.copyWith(providerPriority: sanitized);
_log.d('Saved provider priority: $sanitized');
@@ -811,7 +806,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
}
for (final provider in const ['tidal', 'qobuz', 'amazon', 'deezer']) {
for (final provider in const ['tidal', 'qobuz', 'deezer']) {
if (!result.contains(provider)) {
result.add(provider);
}
@@ -822,7 +817,6 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
Future<void> loadMetadataProviderPriority() async {
try {
// Load from SharedPreferences first (persisted)
final prefs = await SharedPreferences.getInstance();
final savedJson = prefs.getString(_metadataProviderPriorityKey);
@@ -831,10 +825,8 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
final saved = jsonDecode(savedJson) as List<dynamic>;
priority = saved.map((e) => e as String).toList();
_log.d('Loaded metadata provider priority from prefs: $priority');
// Sync to Go backend
await PlatformBridge.setMetadataProviderPriority(priority);
} else {
// Fallback to Go backend default
priority = await PlatformBridge.getMetadataProviderPriority();
_log.d('Using default metadata provider priority: $priority');
}
@@ -847,11 +839,9 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
Future<void> setMetadataProviderPriority(List<String> priority) async {
try {
// Save to SharedPreferences for persistence
final prefs = await SharedPreferences.getInstance();
await prefs.setString(_metadataProviderPriorityKey, jsonEncode(priority));
// Sync to Go backend
await PlatformBridge.setMetadataProviderPriority(priority);
state = state.copyWith(metadataProviderPriority: priority);
_log.d('Saved metadata provider priority: $priority');
@@ -880,7 +870,7 @@ class ExtensionNotifier extends Notifier<ExtensionState> {
}
List<String> getAllDownloadProviders() {
final providers = ['tidal', 'qobuz', 'amazon', 'deezer'];
final providers = ['tidal', 'qobuz', 'deezer'];
for (final ext in state.extensions) {
if (ext.enabled && ext.hasDownloadProvider) {
providers.add(ext.id);
-5
View File
@@ -204,7 +204,6 @@ class TrackNotifier extends Notifier<TrackState> {
state = TrackState(isLoading: true, hasSearchText: state.hasSearchText);
try {
// Step 1: Check for extension URL handlers first (handles YT Music, etc.)
final extensionHandler = await PlatformBridge.findURLHandler(url);
if (extensionHandler != null) {
_log.i('Found extension URL handler: $extensionHandler for URL: $url');
@@ -215,7 +214,6 @@ class TrackNotifier extends Notifier<TrackState> {
result = await PlatformBridge.handleURLWithExtension(url);
if (!_isRequestValid(requestId)) return;
// Check if we got valid data
if (result != null &&
result['type'] == 'track' &&
result['track'] != null) {
@@ -321,7 +319,6 @@ class TrackNotifier extends Notifier<TrackState> {
}
}
// Step 2: Try Deezer URL parsing
if (url.contains('deezer.com') || url.contains('deezer.page.link')) {
_log.i('Detected Deezer URL, parsing...');
final parsed = await PlatformBridge.parseDeezerUrl(url);
@@ -387,7 +384,6 @@ class TrackNotifier extends Notifier<TrackState> {
return;
}
// Step 3: Try Tidal URL parsing
if (url.contains('tidal.com')) {
_log.i('Detected Tidal URL, parsing...');
final parsed = await PlatformBridge.parseTidalUrl(url);
@@ -461,7 +457,6 @@ class TrackNotifier extends Notifier<TrackState> {
return;
}
// Step 4: Fall back to Spotify parsing
final parsed = await PlatformBridge.parseSpotifyUrl(url);
if (!_isRequestValid(requestId)) return;
-3
View File
@@ -305,7 +305,6 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
background: Stack(
fit: StackFit.expand,
children: [
// Full-screen cover background (no blur, full resolution)
if (widget.coverUrl != null)
CachedNetworkImage(
imageUrl:
@@ -326,7 +325,6 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
color: colorScheme.onSurfaceVariant,
),
),
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -345,7 +343,6 @@ class _AlbumScreenState extends ConsumerState<AlbumScreen> {
),
),
),
// Album info overlay at bottom
Positioned(
left: 20,
right: 20,
+2 -10
View File
@@ -21,7 +21,6 @@ import 'package:spotiflac_android/widgets/download_service_picker.dart';
import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
import 'package:spotiflac_android/utils/clickable_metadata.dart';
/// Simple in-memory cache for artist data
class _ArtistCache {
static final Map<String, _CacheEntry> _cache = {};
static const Duration _ttl = Duration(minutes: 10);
@@ -69,7 +68,6 @@ class _CacheEntry {
});
}
/// Artist screen with Spotify-like design
class ArtistScreen extends ConsumerStatefulWidget {
final String artistId;
final String artistName;
@@ -717,7 +715,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
),
),
const Divider(height: 1),
// Options
if (albums.isNotEmpty)
_DiscographyOptionTile(
icon: Icons.library_music,
@@ -830,7 +827,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
int failedCount = 0;
for (final album in albums) {
if (!_isFetchingDiscography) break; // Cancelled
if (!_isFetchingDiscography) break;
try {
final tracks = await _fetchAlbumTracks(album);
@@ -1066,7 +1063,7 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
CachedNetworkImage(
imageUrl: imageUrl,
fit: BoxFit.cover,
alignment: Alignment.topCenter, // Show top of image (faces)
alignment: Alignment.topCenter,
memCacheWidth: 800,
cacheManager: CoverCacheManager.instance,
placeholder: (context, url) =>
@@ -1155,7 +1152,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
],
),
),
// Download Discography button (icon only, right-aligned)
if (hasDiscography && !_isSelectionMode) ...[
const SizedBox(width: 12),
Container(
@@ -1201,7 +1197,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
);
}
/// Build Popular tracks section like Spotify
Widget _buildPopularSection(ColorScheme colorScheme) {
if (_topTracks == null || _topTracks!.isEmpty) {
return const SizedBox.shrink();
@@ -1416,7 +1411,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
);
}
/// Handle tap on popular track item
void _handlePopularTrackTap(Track track, {required bool isQueued}) async {
if (isQueued) return;
@@ -1636,7 +1630,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
),
),
),
// Selection overlay
if (_isSelectionMode)
Positioned.fill(
child: AnimatedContainer(
@@ -1652,7 +1645,6 @@ class _ArtistScreenState extends ConsumerState<ArtistScreen> {
),
),
),
// Checkbox
if (_isSelectionMode)
Positioned(
top: 8,
-11
View File
@@ -18,7 +18,6 @@ import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
/// Screen to display downloaded tracks from a specific album
class DownloadedAlbumScreen extends ConsumerStatefulWidget {
final String albumName;
final String artistName;
@@ -361,7 +360,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
);
final tracks = _getAlbumTracks(allHistoryItems);
// Show empty state if no tracks found
if (tracks.isEmpty) {
return Scaffold(
appBar: AppBar(title: Text(widget.albumName)),
@@ -480,7 +478,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
background: Stack(
fit: StackFit.expand,
children: [
// Full-screen cover background
if (embeddedCoverPath != null)
Image.file(
File(embeddedCoverPath),
@@ -508,7 +505,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
color: colorScheme.onSurfaceVariant,
),
),
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -527,7 +523,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
),
),
),
// Album info overlay at bottom
Positioned(
left: 20,
right: 20,
@@ -711,10 +706,8 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
final discTracks = discMap[discNumber];
if (discTracks == null || discTracks.isEmpty) continue;
// Add disc separator
children.add(_buildDiscSeparator(context, colorScheme, discNumber));
// Add tracks for this disc
for (final track in discTracks) {
children.add(
KeyedSubtree(
@@ -897,7 +890,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
return;
}
// Share SAF content URIs via native intent
if (safUris.isNotEmpty) {
try {
if (safUris.length == 1) {
@@ -908,13 +900,11 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
} catch (_) {}
}
// Share regular files via SharePlus
if (filesToShare.isNotEmpty) {
await SharePlus.instance.share(ShareParams(files: filesToShare));
}
}
/// Show batch convert bottom sheet
void _showBatchConvertSheet(
BuildContext context,
List<DownloadHistoryItem> allTracks,
@@ -1388,7 +1378,6 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
),
const SizedBox(height: 12),
// Action buttons row: Share, Convert
Row(
children: [
Expanded(
+2 -9
View File
@@ -520,7 +520,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
final settings = ref.read(settingsProvider);
final extState = ref.read(extensionProvider);
final searchProvider = settings.searchProvider;
// Use filterOverride if provided, otherwise read from state
final selectedFilter =
filterOverride ?? ref.read(trackProvider).selectedSearchFilter;
@@ -535,7 +534,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
extState.extensions.any((e) => e.id == searchProvider && e.enabled);
if (isExtensionEnabled) {
// Build options with filter if selected
Map<String, dynamic>? options;
if (selectedFilter != null) {
options = {'filter': selectedFilter};
@@ -1116,7 +1114,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
),
),
// Search filter bar (only shown when has search results)
if (hasActualResults && !showRecentAccess)
Consumer(
builder: (context, ref, _) {
@@ -1286,8 +1283,8 @@ class _HomeTabState extends ConsumerState<HomeTab>
),
],
),
), // Close RefreshIndicator
), // Close GestureDetector
),
),
);
}
@@ -1434,7 +1431,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
return _buildExploreSection(sections[sectionIndex], colorScheme);
}
// Bottom padding
return const SizedBox(height: 16);
}, childCount: totalCount),
),
@@ -2705,7 +2701,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
scrollDirection: Axis.horizontal,
child: Row(
children: [
// "All" chip (no filter)
Padding(
padding: const EdgeInsets.only(right: 8),
child: FilterChip(
@@ -2728,7 +2723,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
),
),
),
// Filter chips from extension
...filters.map((filter) {
final isSelected = selectedFilter == filter.id;
return Padding(
@@ -2830,7 +2824,6 @@ class _HomeTabState extends ConsumerState<HomeTab>
prefixIcon: _SearchProviderDropdown(
onProviderChanged: () {
_lastSearchQuery = null;
// Reset filter when provider changes
ref.read(trackProvider.notifier).setSearchFilter(null);
setState(() {});
final text = _urlController.text.trim();
@@ -158,7 +158,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header: drag handle + thumbnail + playlist info
Column(
children: [
const SizedBox(height: 8),
@@ -210,7 +209,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
color: colorScheme.outlineVariant.withValues(alpha: 0.5),
),
// Rename
_PlaylistOptionTile(
icon: Icons.edit_outlined,
title: context.l10n.collectionRenamePlaylist,
@@ -225,7 +223,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
},
),
// Change cover
_PlaylistOptionTile(
icon: Icons.image_outlined,
title: context.l10n.collectionPlaylistChangeCover,
@@ -235,7 +232,6 @@ class LibraryPlaylistsScreen extends ConsumerWidget {
},
),
// Delete
_PlaylistOptionTile(
icon: Icons.delete_outline,
iconColor: colorScheme.error,
@@ -37,7 +37,6 @@ class _LibraryTracksFolderScreenState
bool _showTitleInAppBar = false;
final ScrollController _scrollController = ScrollController();
// ── Multi-select state ──
bool _isSelectionMode = false;
final Set<String> _selectedKeys = {};
@@ -145,8 +144,6 @@ class _LibraryTracksFolderScreenState
return url;
}
// ── Selection helpers ──
void _enterSelectionMode(String key) {
HapticFeedback.mediumImpact();
setState(() {
@@ -181,8 +178,6 @@ class _LibraryTracksFolderScreenState
});
}
// ── Batch actions ──
Future<void> _removeSelected(List<CollectionTrackEntry> entries) async {
final keysToRemove = _selectedKeys.toSet();
if (keysToRemove.isEmpty) return;
@@ -426,7 +421,6 @@ class _LibraryTracksFolderScreenState
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Drag handle
Container(
width: 32,
height: 4,
@@ -437,7 +431,6 @@ class _LibraryTracksFolderScreenState
),
),
// Header: [X close] [count] [Select All / Deselect]
Row(
children: [
IconButton.filledTonal(
@@ -493,7 +486,6 @@ class _LibraryTracksFolderScreenState
const SizedBox(height: 12),
// Action buttons row
Row(
children: [
if (isWishlist)
@@ -525,7 +517,6 @@ class _LibraryTracksFolderScreenState
const SizedBox(height: 8),
// Remove button (full width, red)
SizedBox(
width: double.infinity,
child: FilledButton.icon(
@@ -714,7 +705,6 @@ class _LibraryTracksFolderScreenState
)
else
coverFallback,
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -733,7 +723,6 @@ class _LibraryTracksFolderScreenState
),
),
),
// Title and track count overlay
Positioned(
left: 20,
right: 20,
@@ -829,8 +818,6 @@ class _LibraryTracksFolderScreenState
);
}
// ── Header actions ──
Widget _buildHeaderActionPlaceholder() => const SizedBox(width: 48, height: 48);
Widget _buildDownloadAllCenterButton(List<CollectionTrackEntry> entries) {
@@ -1263,7 +1250,6 @@ class _CollectionTrackTile extends ConsumerWidget {
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
// Header: drag handle + cover + track info
Column(
children: [
const SizedBox(height: 8),
-15
View File
@@ -13,7 +13,6 @@ import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/providers/local_library_provider.dart';
import 'package:spotiflac_android/providers/playback_provider.dart';
/// Screen to display tracks from a local library album
class LocalAlbumScreen extends ConsumerStatefulWidget {
final String albumName;
final String artistName;
@@ -83,7 +82,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
List<LocalLibraryItem> _buildSortedTracks() {
final tracks = List<LocalLibraryItem>.from(widget.tracks);
tracks.sort((a, b) {
// Sort by disc number first, then by track number
final aDisc = a.discNumber ?? 1;
final bDisc = b.discNumber ?? 1;
if (aDisc != bDisc) return aDisc.compareTo(bDisc);
@@ -197,7 +195,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
),
);
// Go back if all tracks were deleted
if (deletedCount == currentTracks.length) {
Navigator.pop(context);
}
@@ -233,7 +230,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
final bottomPadding = MediaQuery.of(context).padding.bottom;
final tracks = _sortedTracksCache;
// Show empty state if no tracks found
if (tracks.isEmpty) {
return Scaffold(
appBar: AppBar(title: Text(widget.albumName)),
@@ -326,7 +322,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
background: Stack(
fit: StackFit.expand,
children: [
// Full-screen cover background
if (widget.coverPath != null)
Image.file(
File(widget.coverPath!),
@@ -343,7 +338,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
color: colorScheme.onSurfaceVariant,
),
),
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -362,7 +356,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
),
),
),
// Album info overlay at bottom
Positioned(
left: 20,
right: 20,
@@ -888,7 +881,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
return false;
}
/// Batch re-enrich selected local tracks
Future<void> _reEnrichSelected(List<LocalLibraryItem> allTracks) async {
final tracksById = {for (final t in allTracks) t.id: t};
final selected = <LocalLibraryItem>[];
@@ -988,7 +980,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
).showSnackBar(SnackBar(content: Text(summary)));
}
/// Show batch convert bottom sheet
void _showBatchConvertSheet(
BuildContext context,
List<LocalLibraryItem> allTracks,
@@ -1261,7 +1252,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
String? safTempPath;
if (isSaf) {
// Copy SAF file to temp for conversion
safTempPath = await PlatformBridge.copyContentUriToTemp(
item.filePath,
);
@@ -1296,7 +1286,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
if (isSaf) {
// For SAF: derive the parent tree URI and relative dir from the content URI,
// then create new SAF file and delete old one
//
// Parse the SAF URI to get the tree document path:
// content://...tree/...document/.../oldName.flac
// We need tree URI and relative dir to create the new file
@@ -1375,14 +1364,12 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
continue;
}
// Delete old SAF file
try {
await PlatformBridge.safDelete(item.filePath);
} catch (_) {}
await localDb.deleteByPath(item.filePath);
}
// Clean up temp files
try {
await File(newPath).delete();
} catch (_) {}
@@ -1400,7 +1387,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
} catch (_) {}
}
// Reload local library to pick up converted files
ref.read(localLibraryProvider.notifier).reloadFromStorage();
_exitSelectionMode();
@@ -1513,7 +1499,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen> {
),
const SizedBox(height: 12),
// Action buttons row: Re-enrich, Convert
Row(
children: [
Expanded(
-5
View File
@@ -206,7 +206,6 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
background: Stack(
fit: StackFit.expand,
children: [
// Full-screen cover background
if (widget.coverUrl != null)
CachedNetworkImage(
imageUrl:
@@ -227,7 +226,6 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
color: colorScheme.onSurfaceVariant,
),
),
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -246,7 +244,6 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
),
),
),
// Playlist info overlay at bottom
Positioned(
left: 20,
right: 20,
@@ -436,8 +433,6 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
}
}
// ── Shuffle / Love / Download buttons ──
Widget _buildCircleButton({
required IconData icon,
required String tooltip,
+3 -11
View File
@@ -211,7 +211,7 @@ class _GroupedAlbum {
class _GroupedLocalAlbum {
final String albumName;
final String artistName;
final String? coverPath; // Local cover file path
final String? coverPath;
final List<LocalLibraryItem> tracks;
final DateTime latestScanned;
final String searchKey;
@@ -229,12 +229,11 @@ class _GroupedLocalAlbum {
class _HistoryStats {
final Map<String, int> albumCounts;
final Map<String, int> localAlbumCounts; // For identifying local singles
final Map<String, int> localAlbumCounts;
final List<_GroupedAlbum> groupedAlbums;
final List<_GroupedLocalAlbum> groupedLocalAlbums; // Local library albums
final List<_GroupedLocalAlbum> groupedLocalAlbums;
final int albumCount;
final int singleTracks;
// Local library stats
final int localAlbumCount;
final int localSingleTracks;
@@ -933,8 +932,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
overlay.insert(_playlistSelectionOverlayEntry!);
}
// --- Playlist selection mode ---
void _enterPlaylistSelectionMode(String playlistId) {
HapticFeedback.mediumImpact();
setState(() {
@@ -1202,11 +1199,9 @@ class _QueueTabState extends ConsumerState<QueueTab> {
await deleteFile(cleanPath);
} catch (_) {}
// Remove from appropriate database
if (item.source == LibraryItemSource.downloaded) {
historyNotifier.removeFromHistory(item.historyItem!.id);
} else {
// Remove from local library database
await localLibraryDb.deleteByPath(item.filePath);
}
deletedCount++;
@@ -2024,7 +2019,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
Map<String, int> albumCounts, [
String searchQuery = '',
]) {
// First apply search filter
var filteredItems = items;
if (searchQuery.isNotEmpty) {
final query = searchQuery;
@@ -2034,7 +2028,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
}).toList();
}
// Then apply filter mode
if (filterMode == 'all') return filteredItems;
switch (filterMode) {
@@ -2639,7 +2632,6 @@ class _QueueTabState extends ConsumerState<QueueTab> {
),
),
// Search bar - always at top
if (allHistoryItems.isNotEmpty ||
hasQueueItems ||
localLibraryItems.isNotEmpty)
+1 -4
View File
@@ -49,7 +49,7 @@ class AboutPage extends StatelessWidget {
title: Text(
context.l10n.aboutTitle,
style: TextStyle(
fontSize: 20 + (8 * expandRatio), // 20 -> 28
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
@@ -462,7 +462,6 @@ class _ContributorItem extends StatelessWidget {
}
}
/// Translator data model
class _Translator {
final String name;
final String crowdinUsername;
@@ -477,7 +476,6 @@ class _Translator {
});
}
/// Translators section with compact chip-style layout
class _TranslatorsSection extends StatelessWidget {
const _TranslatorsSection();
@@ -558,7 +556,6 @@ class _TranslatorsSection extends StatelessWidget {
}
}
/// Individual translator chip
class _TranslatorChip extends StatelessWidget {
final _Translator translator;
@@ -148,7 +148,6 @@ class AppearanceSettingsPage extends ConsumerWidget {
}
}
/// A simplified preview of how the app looks with current settings
class _ThemePreviewCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
@@ -423,7 +422,6 @@ class _ColorPaletteItem extends StatelessWidget {
}
}
/// Optimized app bar title with animation
class _AppBarTitle extends StatelessWidget {
final String title;
final double topPadding;
@@ -440,14 +438,14 @@ class _AppBarTitle extends StatelessWidget {
final expandRatio =
((constraints.maxHeight - minHeight) / (maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio); // 56 -> 24
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
titlePadding: EdgeInsets.only(left: leftPadding, bottom: 16),
title: Text(
title,
style: TextStyle(
fontSize: 20 + (8 * expandRatio), // 20 -> 28
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
-3
View File
@@ -56,17 +56,14 @@ class DonatePage extends StatelessWidget {
padding: const EdgeInsets.all(16),
child: Column(
children: [
// Donate links card
_DonateLinksCard(colorScheme: colorScheme),
const SizedBox(height: 24),
// Recent donors section
_RecentDonorsCard(colorScheme: colorScheme),
const SizedBox(height: 16),
// Combined notice card
Card(
elevation: 0,
color: colorScheme.secondaryContainer.withValues(
@@ -23,7 +23,7 @@ class DownloadSettingsPage extends ConsumerStatefulWidget {
}
class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
static const _builtInServices = ['tidal', 'qobuz', 'amazon', 'deezer'];
static const _builtInServices = ['tidal', 'qobuz', 'deezer'];
static const _songLinkRegions = [
'AD',
'AE',
@@ -326,7 +326,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio); // 56 -> 24
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
titlePadding: EdgeInsets.only(
@@ -336,7 +336,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
title: Text(
context.l10n.downloadTitle,
style: TextStyle(
fontSize: 20 + (8 * expandRatio), // 20 -> 28
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
@@ -450,7 +450,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
const SizedBox(width: 8),
Expanded(
child: Text(
'Select Tidal, Qobuz, or Amazon above to configure quality',
'Select Tidal or Qobuz above to configure quality',
style: Theme.of(context).textTheme.bodySmall
?.copyWith(
color: colorScheme.onSurfaceVariant,
@@ -720,7 +720,6 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
),
),
// Download Network Mode
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.sectionDownload),
),
@@ -778,7 +777,6 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
),
),
// All Files Access section (Android 13+ only)
if (Platform.isAndroid && _androidSdkVersion >= 33) ...[
SliverToBoxAdapter(
child: SettingsSectionHeader(
@@ -2039,7 +2037,7 @@ class _ServiceSelector extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final extState = ref.watch(extensionProvider);
final builtInServiceIds = ['tidal', 'qobuz', 'amazon', 'deezer', 'youtube'];
final builtInServiceIds = ['tidal', 'qobuz', 'deezer', 'youtube'];
final extensionProviders = extState.extensions
.where((e) => e.enabled && e.hasDownloadProvider)
@@ -2076,15 +2074,6 @@ class _ServiceSelector extends ConsumerWidget {
),
),
const SizedBox(width: 8),
Expanded(
child: _ServiceChip(
icon: Icons.shopping_bag_outlined,
label: 'Amazon',
isSelected: effectiveService == 'amazon',
onTap: () => onChanged('amazon'),
),
),
const SizedBox(width: 8),
Expanded(
child: _ServiceChip(
icon: Icons.smart_display,
@@ -658,7 +658,6 @@ class _SettingItemState extends State<_SettingItem> {
);
}
// For button type, show a different layout
if (widget.setting.type == 'button') {
return Column(
mainAxisSize: MainAxisSize.min,
@@ -271,7 +271,6 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
),
),
// Scan Settings Section
SliverToBoxAdapter(
child: SettingsSectionHeader(
title: context.l10n.libraryScanSettings,
@@ -442,7 +441,6 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
),
],
// Info Section
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
@@ -558,7 +556,6 @@ class _LibraryHeroCard extends StatelessWidget {
clipBehavior: Clip.antiAlias,
child: Stack(
children: [
// Background decorative elements
Positioned(
right: -20,
top: -20,
@@ -581,7 +578,6 @@ class _LibraryHeroCard extends StatelessWidget {
),
),
// Content
Padding(
padding: const EdgeInsets.all(24),
child: Column(
@@ -122,8 +122,6 @@ class _LyricsProviderPriorityPageState
);
}
// ── State mutations ──
void _enableProvider(String id) {
setState(() => _enabledProviders.add(id));
_markChanged();
@@ -142,8 +140,6 @@ class _LyricsProviderPriorityPageState
_markChanged();
}
// ── Save / Discard ──
Future<void> _saveChanges() async {
ref
.read(settingsProvider.notifier)
@@ -180,8 +176,6 @@ class _LyricsProviderPriorityPageState
return result ?? false;
}
// ── Provider metadata ──
static _LyricsProviderInfo _getLyricsProviderInfo(String id) {
switch (id) {
case 'spotify_api':
@@ -230,10 +224,6 @@ class _LyricsProviderPriorityPageState
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Enabled provider card (reorderable)
// ═══════════════════════════════════════════════════════════════════════════
class _EnabledProviderItem extends StatelessWidget {
final String providerId;
final _LyricsProviderInfo info;
@@ -273,7 +263,6 @@ class _EnabledProviderItem extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
child: Row(
children: [
// Numbered badge
Container(
width: 28,
height: 28,
@@ -296,10 +285,8 @@ class _EnabledProviderItem extends StatelessWidget {
),
),
const SizedBox(width: 16),
// Icon
Icon(info.icon, color: colorScheme.primary),
const SizedBox(width: 12),
// Name + description
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -319,7 +306,6 @@ class _EnabledProviderItem extends StatelessWidget {
],
),
),
// Enable/disable switch
SizedBox(
height: 32,
child: FittedBox(
@@ -327,7 +313,6 @@ class _EnabledProviderItem extends StatelessWidget {
),
),
const SizedBox(width: 4),
// Drag handle
Icon(Icons.drag_handle, color: colorScheme.onSurfaceVariant),
],
),
@@ -338,10 +323,6 @@ class _EnabledProviderItem extends StatelessWidget {
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Disabled provider card
// ═══════════════════════════════════════════════════════════════════════════
class _DisabledProviderItem extends StatelessWidget {
final String providerId;
final _LyricsProviderInfo info;
@@ -383,10 +364,8 @@ class _DisabledProviderItem extends StatelessWidget {
// Empty space aligned with numbered badge
const SizedBox(width: 28),
const SizedBox(width: 16),
// Icon (muted)
Icon(info.icon, color: colorScheme.outline),
const SizedBox(width: 12),
// Name + description
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@@ -407,7 +386,6 @@ class _DisabledProviderItem extends StatelessWidget {
],
),
),
// Switch
SizedBox(
height: 32,
child: FittedBox(
@@ -424,10 +402,6 @@ class _DisabledProviderItem extends StatelessWidget {
}
}
// ═══════════════════════════════════════════════════════════════════════════
// Provider info model
// ═══════════════════════════════════════════════════════════════════════════
class _LyricsProviderInfo {
final String name;
final String description;
@@ -43,7 +43,7 @@ class OptionsSettingsPage extends ConsumerWidget {
((constraints.maxHeight - minHeight) /
(maxHeight - minHeight))
.clamp(0.0, 1.0);
final leftPadding = 56 - (32 * expandRatio); // 56 -> 24
final leftPadding = 56 - (32 * expandRatio);
return FlexibleSpaceBar(
expandedTitleScale: 1.0,
titlePadding: EdgeInsets.only(
@@ -53,7 +53,7 @@ class OptionsSettingsPage extends ConsumerWidget {
title: Text(
context.l10n.optionsTitle,
style: TextStyle(
fontSize: 20 + (8 * expandRatio), // 20 -> 28
fontSize: 20 + (8 * expandRatio),
fontWeight: FontWeight.bold,
color: colorScheme.onSurface,
),
@@ -331,7 +331,6 @@ class OptionsSettingsPage extends ConsumerWidget {
BuildContext context,
WidgetRef ref,
) async {
// Show loading indicator
showDialog(
context: context,
barrierDismissible: false,
@@ -333,12 +333,6 @@ class _ProviderItem extends StatelessWidget {
);
case 'qobuz':
return _ProviderInfo(name: 'Qobuz', icon: Icons.album, isBuiltIn: true);
case 'amazon':
return _ProviderInfo(
name: 'Amazon Music',
icon: Icons.shopping_bag,
isBuiltIn: true,
);
case 'youtube':
return _ProviderInfo(
name: 'YouTube',
+1 -6
View File
@@ -22,7 +22,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
final PageController _pageController = PageController();
int _currentStep = 0;
// State variables
bool _storagePermissionGranted = false;
bool _notificationPermissionGranted = false;
String? _selectedDirectory;
@@ -474,7 +473,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
Widget build(BuildContext context) {
final colorScheme = Theme.of(context).colorScheme;
// Calculate progress
final progress = (_currentStep + 1) / _totalSteps;
return Scaffold(
@@ -482,7 +480,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
body: SafeArea(
child: Column(
children: [
// Top Bar
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 16),
child: Row(
@@ -497,9 +494,8 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
),
)
else
const SizedBox(width: 48), // Spacer
const SizedBox(width: 48),
const Spacer(),
// Progress Indicator
SizedBox(
width: 48,
height: 48,
@@ -530,7 +526,6 @@ class _SetupScreenState extends ConsumerState<SetupScreen> {
),
),
// Content
Expanded(
child: PageView(
controller: _pageController,
-15
View File
@@ -627,7 +627,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
return Stack(
fit: StackFit.expand,
children: [
// Full-screen cover background
if (_hasPath(_embeddedCoverPreviewPath))
Image.file(
File(_embeddedCoverPreviewPath!),
@@ -657,7 +656,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
color: colorScheme.onSurfaceVariant,
),
),
// Bottom gradient for readability
Positioned(
left: 0,
right: 0,
@@ -676,7 +674,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
),
),
),
// Track info overlay at bottom
Positioned(
left: 20,
right: 20,
@@ -2606,7 +2603,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
),
const SizedBox(height: 20),
// Target format
Text(
context.l10n.trackConvertTargetFormat,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
@@ -2639,7 +2635,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
),
const SizedBox(height: 16),
// Bitrate
Text(
context.l10n.trackConvertBitrate,
style: Theme.of(context).textTheme.titleSmall?.copyWith(
@@ -2664,7 +2659,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
),
const SizedBox(height: 24),
// Convert button
SizedBox(
width: double.infinity,
child: FilledButton(
@@ -2750,7 +2744,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
SnackBar(content: Text(context.l10n.trackConvertConverting)),
);
// Step 1: Read metadata from file (fallback to known item metadata).
final metadata = _buildFallbackMetadata();
try {
final result = await PlatformBridge.readFileMetadata(cleanFilePath);
@@ -2768,7 +2761,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
_log.w('readFileMetadata threw, using fallback metadata: $e');
}
// Step 2: Extract cover art to temp file
String? coverPath;
try {
final tempDir = await getTemporaryDirectory();
@@ -2783,7 +2775,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
}
} catch (_) {}
// Step 3: Handle SAF vs regular file
String workingPath = cleanFilePath;
final isSaf = _isSafFile;
String? safTempPath;
@@ -2803,7 +2794,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
workingPath = safTempPath;
}
// Step 4: Convert
final newPath = await FFmpegService.convertAudioFormat(
inputPath: workingPath,
targetFormat: targetFormat.toLowerCase(),
@@ -2838,7 +2828,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
final newQuality = _buildConvertedQualityLabel(targetFormat, bitrate);
// Step 5: Handle SAF write-back
if (isSaf) {
final treeUri = _downloadItem?.downloadTreeUri;
final relativeDir = _downloadItem?.safRelativeDir ?? '';
@@ -3064,7 +3053,6 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
// Also remove from local library database
// ref.read(localLibraryProvider.notifier).removeItem(_localLibraryItem!.id);
} else {
// Existing download history deletion logic
try {
await deleteFile(cleanFilePath);
} catch (e) {
@@ -3661,7 +3649,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
expand: false,
builder: (context, scrollController) => Column(
children: [
// Handle bar
Padding(
padding: const EdgeInsets.only(top: 12, bottom: 8),
child: Container(
@@ -3673,7 +3660,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
),
),
),
// Title row
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: Row(
@@ -3698,7 +3684,6 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
),
),
const SizedBox(height: 12),
// Fields
Expanded(
child: ListView(
controller: scrollController,
-9
View File
@@ -90,7 +90,6 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
body: SafeArea(
child: Column(
children: [
// Top Navigation Bar
Padding(
padding: EdgeInsets.symmetric(
horizontal: topBarPaddingH,
@@ -112,7 +111,6 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
),
),
// Skip button
TextButton(
onPressed: _skipTutorial,
style: TextButton.styleFrom(
@@ -131,7 +129,6 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
),
),
// Main Content Area
Expanded(
child: PageView(
controller: _pageController,
@@ -218,12 +215,10 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
),
),
// Bottom Control Area
Padding(
padding: const EdgeInsets.all(24),
child: Column(
children: [
// Expressive Page Indicators
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(_totalPages, (index) {
@@ -246,7 +241,6 @@ class _TutorialScreenState extends ConsumerState<TutorialScreen> {
}),
),
SizedBox(height: bottomGap),
// Action Button
SizedBox(
width: double.infinity,
height: actionButtonHeight,
@@ -402,7 +396,6 @@ class _InteractiveSearchExampleState extends State<_InteractiveSearchExample> {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Search Input
TextField(
controller: _controller,
onChanged: (value) {
@@ -428,7 +421,6 @@ class _InteractiveSearchExampleState extends State<_InteractiveSearchExample> {
),
),
// Result Placeholder
AnimatedSize(
duration: const Duration(milliseconds: 400),
curve: Curves.easeOutBack,
@@ -541,7 +533,6 @@ class _InteractiveDownloadExampleState
_isCompleted = true;
});
// Reset after a delay
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
setState(() {
+6 -6
View File
@@ -198,7 +198,7 @@ class FFmpegService {
final trimmedKey = decryptionKey.trim();
if (trimmedKey.isEmpty) return inputPath;
// Amazon encrypted streams are commonly MP4 container with FLAC audio.
// Encrypted streams are commonly MP4 container with FLAC audio.
// Prefer FLAC output to avoid MP4 muxing errors during decrypt copy.
final preferredExt = inputPath.toLowerCase().endsWith('.m4a')
? '.flac'
@@ -217,7 +217,10 @@ class FFmpegService {
required String key,
}) {
final audioMap = mapAudioOnly ? '-map 0:a ' : '';
return '-v error -decryption_key "$key" -i "$inputPath" $audioMap-c copy "$outputPath" -y';
// Force MOV demuxer: -decryption_key is only supported by the MOV/MP4
// demuxer. The input may carry a .flac extension (SAF mode) while actually
// containing an encrypted M4A stream, so we must override auto-detection.
return '-v error -decryption_key "$key" -f mov -i "$inputPath" $audioMap-c copy "$outputPath" -y';
}
final keyCandidates = _buildDecryptionKeyCandidates(trimmedKey);
@@ -627,7 +630,7 @@ class FFmpegService {
return null;
}
static Future<LiveDecryptedStreamResult?> startAmazonLiveDecryptedStream({
static Future<LiveDecryptedStreamResult?> startEncryptedLiveDecryptedStream({
required String encryptedStreamUrl,
required String decryptionKey,
String preferredFormat = 'flac',
@@ -1225,7 +1228,6 @@ class FFmpegService {
final extension = format == 'opus' ? '.opus' : '.mp3';
final outputPath = _buildOutputPath(inputPath, extension);
// Step 1: Convert audio
String command;
if (format == 'opus') {
command =
@@ -1245,7 +1247,6 @@ class FFmpegService {
return null;
}
// Step 2: Embed metadata + cover into the converted file.
// Treat embed failure as conversion failure when metadata/cover was requested.
final hasMetadata = metadata.values.any((v) => v.trim().isNotEmpty);
final hasCover = coverPath != null && coverPath.trim().isNotEmpty;
@@ -1281,7 +1282,6 @@ class FFmpegService {
}
}
// Step 3: Delete original if requested
if (deleteOriginal) {
try {
await File(inputPath).delete();
-4
View File
@@ -104,8 +104,6 @@ class HistoryDatabase {
}
}
// ==================== iOS Path Normalization ====================
/// Pattern to match iOS container paths
/// Example: /var/mobile/Containers/Data/Application/UUID-HERE/Documents/...
static final _iosContainerPattern = RegExp(
@@ -325,8 +323,6 @@ class HistoryDatabase {
};
}
// ==================== CRUD Operations ====================
/// Insert or update a history item
Future<void> upsert(Map<String, dynamic> json) async {
final db = await database;
-5
View File
@@ -403,8 +403,6 @@ class PlatformBridge {
return jsonDecode(result as String) as Map<String, dynamic>;
}
// ==================== LYRICS PROVIDER SETTINGS ====================
/// Sets the lyrics provider order. Providers not in the list are disabled.
static Future<void> setLyricsProviders(List<String> providers) async {
final providersJSON = jsonEncode(providers);
@@ -1060,8 +1058,6 @@ class PlatformBridge {
}
}
// ==================== LOCAL LIBRARY SCANNING ====================
/// Set the directory for caching extracted cover art
static Future<void> setLibraryCoverCacheDir(String cacheDir) async {
_log.i('setLibraryCoverCacheDir: $cacheDir');
@@ -1261,5 +1257,4 @@ class PlatformBridge {
await _channel.invokeMethod('clearStoreCache');
}
// ==================== YOUTUBE / COBALT ====================
}
-2
View File
@@ -28,14 +28,12 @@ class _KofiPainter extends CustomPainter {
..color = color
..style = PaintingStyle.fill;
// Cup body
final cup = RRect.fromRectAndRadius(
Rect.fromLTWH(s * 0.08, s * 0.28, s * 0.62, s * 0.52),
Radius.circular(s * 0.12),
);
canvas.drawRRect(cup, paint);
// Handle
final handlePaint = Paint()
..color = color
..style = PaintingStyle.stroke
-12
View File
@@ -67,17 +67,6 @@ const _builtInServices = [
),
],
),
BuiltInService(
id: 'amazon',
label: 'Amazon',
qualityOptions: [
QualityOption(
id: 'LOSSLESS',
label: 'FLAC Best Available',
description: 'Amazon API delivers the best available lossless quality',
),
],
),
BuiltInService(
id: 'deezer',
label: 'Deezer',
@@ -209,7 +198,6 @@ class _DownloadServicePickerState extends ConsumerState<DownloadServicePicker> {
return ext.qualityOptions;
}
// Default fallback options
return [
const QualityOption(
id: 'DEFAULT',