From e187ac461ddfd3b884c2bcab17c010730efe787a Mon Sep 17 00:00:00 2001 From: zarzet Date: Mon, 4 May 2026 00:51:52 +0700 Subject: [PATCH] fix provider fallbacks and public branding --- android/app/src/main/AndroidManifest.xml | 2 +- apps.json | 6 +- go_backend/extension_runtime_http.go | 26 ++- ios/Runner/Info.plist | 6 +- lib/app.dart | 3 +- lib/l10n/app_localizations.dart | 4 +- lib/l10n/app_localizations_de.dart | 6 +- lib/l10n/app_localizations_en.dart | 6 +- lib/l10n/app_localizations_es.dart | 12 +- lib/l10n/app_localizations_fr.dart | 6 +- lib/l10n/app_localizations_hi.dart | 6 +- lib/l10n/app_localizations_id.dart | 6 +- lib/l10n/app_localizations_ja.dart | 6 +- lib/l10n/app_localizations_ko.dart | 6 +- lib/l10n/app_localizations_nl.dart | 6 +- lib/l10n/app_localizations_pt.dart | 12 +- lib/l10n/app_localizations_ru.dart | 6 +- lib/l10n/app_localizations_tr.dart | 6 +- lib/l10n/app_localizations_uk.dart | 6 +- lib/l10n/app_localizations_zh.dart | 18 +- lib/l10n/arb/app_de.arb | 6 +- lib/l10n/arb/app_en.arb | 6 +- lib/l10n/arb/app_es.arb | 6 +- lib/l10n/arb/app_es_ES.arb | 6 +- lib/l10n/arb/app_fr.arb | 6 +- lib/l10n/arb/app_hi.arb | 6 +- lib/l10n/arb/app_id.arb | 6 +- lib/l10n/arb/app_ja.arb | 6 +- lib/l10n/arb/app_ko.arb | 6 +- lib/l10n/arb/app_nl.arb | 6 +- lib/l10n/arb/app_pt.arb | 6 +- lib/l10n/arb/app_pt_PT.arb | 6 +- lib/l10n/arb/app_ru.arb | 6 +- lib/l10n/arb/app_tr.arb | 6 +- lib/l10n/arb/app_uk.arb | 6 +- lib/l10n/arb/app_zh.arb | 6 +- lib/l10n/arb/app_zh_CN.arb | 6 +- lib/l10n/arb/app_zh_TW.arb | 6 +- lib/providers/explore_provider.dart | 143 +++++++++++---- lib/providers/extension_provider.dart | 84 +++++++-- lib/providers/settings_provider.dart | 83 +++++---- lib/providers/track_provider.dart | 12 +- lib/screens/home_tab.dart | 44 ++++- lib/services/notification_service.dart | 5 +- lib/utils/clickable_metadata.dart | 219 ++++++++++++++++++++--- 45 files changed, 615 insertions(+), 238 deletions(-) diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 219982f8..4a652acd 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -18,7 +18,7 @@ maxExtensionHTTPResponseBytes { + return nil, fmt.Errorf( + "response body exceeds %d byte limit; use file.download for large media", + maxExtensionHTTPResponseBytes, + ) + } + return body, nil +} + func (r *extensionRuntime) validateDomain(urlStr string) error { parsed, err := url.Parse(urlStr) if err != nil { @@ -99,7 +117,7 @@ func (r *extensionRuntime) httpGet(call goja.FunctionCall) goja.Value { } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := readExtensionHTTPResponseBody(resp) if err != nil { return r.vm.ToValue(map[string]interface{}{ "error": err.Error(), @@ -197,7 +215,7 @@ func (r *extensionRuntime) httpPost(call goja.FunctionCall) goja.Value { } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := readExtensionHTTPResponseBody(resp) if err != nil { return r.vm.ToValue(map[string]interface{}{ "error": err.Error(), @@ -307,7 +325,7 @@ func (r *extensionRuntime) httpRequest(call goja.FunctionCall) goja.Value { } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := readExtensionHTTPResponseBody(resp) if err != nil { return r.vm.ToValue(map[string]interface{}{ "error": err.Error(), @@ -433,7 +451,7 @@ func (r *extensionRuntime) httpMethodShortcut(method string, call goja.FunctionC } defer resp.Body.Close() - body, err := io.ReadAll(resp.Body) + body, err := readExtensionHTTPResponseBody(resp) if err != nil { return r.vm.ToValue(map[string]interface{}{ "error": err.Error(), diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 5b1d9f87..5e04753a 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -22,7 +22,7 @@ zh-Hant CFBundleDisplayName - SpotiFLAC + SpotiFLAC Mobile CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -30,7 +30,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - SpotiFLAC + SpotiFLAC Mobile CFBundlePackageType APPL CFBundleShortVersionString @@ -80,7 +80,7 @@ NSPhotoLibraryUsageDescription - SpotiFLAC needs access to save album artwork + SpotiFLAC Mobile needs access to save album artwork CFBundleURLTypes diff --git a/lib/app.dart b/lib/app.dart index c6b12ec0..fd68d9e3 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:go_router/go_router.dart'; +import 'package:spotiflac_android/constants/app_info.dart'; import 'package:spotiflac_android/screens/main_shell.dart'; import 'package:spotiflac_android/screens/setup_screen.dart'; import 'package:spotiflac_android/screens/tutorial_screen.dart'; @@ -105,7 +106,7 @@ class SpotiFLACApp extends ConsumerWidget { return DynamicColorWrapper( builder: (lightTheme, darkTheme, themeMode) { return MaterialApp.router( - title: 'SpotiFLAC', + title: AppInfo.appName, debugShowCheckedModeBanner: false, theme: lightTheme, darkTheme: darkTheme, diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index e5544b13..7c756a6e 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -5912,7 +5912,7 @@ abstract class AppLocalizations { /// Notification title while downloading an app update /// /// In en, this message translates to: - /// **'Downloading SpotiFLAC v{version}'** + /// **'Downloading SpotiFLAC Mobile v{version}'** String notifDownloadingUpdate(String version); /// Notification body showing update download progress @@ -5930,7 +5930,7 @@ abstract class AppLocalizations { /// Notification body when app update is ready to install /// /// In en, this message translates to: - /// **'SpotiFLAC v{version} downloaded. Tap to install.'** + /// **'SpotiFLAC Mobile v{version} downloaded. Tap to install.'** String notifUpdateReadyBody(String version); /// Notification title when app update download fails diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 1768330d..78452e4b 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -9,7 +9,7 @@ class AppLocalizationsDe extends AppLocalizations { AppLocalizationsDe([String locale = 'de']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Startseite'; @@ -3508,7 +3508,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3521,7 +3521,7 @@ class AppLocalizationsDe extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index fa8f0ad6..80635c72 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -9,7 +9,7 @@ class AppLocalizationsEn extends AppLocalizations { AppLocalizationsEn([String locale = 'en']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3473,7 +3473,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3486,7 +3486,7 @@ class AppLocalizationsEn extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 7d5e362f..8ed750dc 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -9,7 +9,7 @@ class AppLocalizationsEs extends AppLocalizations { AppLocalizationsEs([String locale = 'es']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3473,7 +3473,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3486,7 +3486,7 @@ class AppLocalizationsEs extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override @@ -3788,7 +3788,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { AppLocalizationsEsEs() : super('es_ES'); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Inicio'; @@ -7211,7 +7211,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -7224,7 +7224,7 @@ class AppLocalizationsEsEs extends AppLocalizationsEs { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d4b8eb0c..fcfdc5e4 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -9,7 +9,7 @@ class AppLocalizationsFr extends AppLocalizations { AppLocalizationsFr([String locale = 'fr']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Accueil'; @@ -3477,7 +3477,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3490,7 +3490,7 @@ class AppLocalizationsFr extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 0884b6f0..97e537dc 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -9,7 +9,7 @@ class AppLocalizationsHi extends AppLocalizations { AppLocalizationsHi([String locale = 'hi']) : super(locale); @override - String get appName => 'SpotiFlac'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'होम'; @@ -3474,7 +3474,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3487,7 +3487,7 @@ class AppLocalizationsHi extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 513fc229..0e5becbf 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -9,7 +9,7 @@ class AppLocalizationsId extends AppLocalizations { AppLocalizationsId([String locale = 'id']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Beranda'; @@ -3483,7 +3483,7 @@ class AppLocalizationsId extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3496,7 +3496,7 @@ class AppLocalizationsId extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 09ae9e94..8fb48c8b 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -9,7 +9,7 @@ class AppLocalizationsJa extends AppLocalizations { AppLocalizationsJa([String locale = 'ja']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'ホーム'; @@ -3461,7 +3461,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3474,7 +3474,7 @@ class AppLocalizationsJa extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index d0da5cf1..90b34b6a 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -9,7 +9,7 @@ class AppLocalizationsKo extends AppLocalizations { AppLocalizationsKo([String locale = 'ko']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3454,7 +3454,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3467,7 +3467,7 @@ class AppLocalizationsKo extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 6f9abd68..0a410227 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -9,7 +9,7 @@ class AppLocalizationsNl extends AppLocalizations { AppLocalizationsNl([String locale = 'nl']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3474,7 +3474,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3487,7 +3487,7 @@ class AppLocalizationsNl extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 7e3747ae..3f2ad316 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -9,7 +9,7 @@ class AppLocalizationsPt extends AppLocalizations { AppLocalizationsPt([String locale = 'pt']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3473,7 +3473,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3486,7 +3486,7 @@ class AppLocalizationsPt extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override @@ -3788,7 +3788,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { AppLocalizationsPtPt() : super('pt_PT'); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Início'; @@ -7204,7 +7204,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -7217,7 +7217,7 @@ class AppLocalizationsPtPt extends AppLocalizationsPt { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 88100e9d..62e43be4 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -9,7 +9,7 @@ class AppLocalizationsRu extends AppLocalizations { AppLocalizationsRu([String locale = 'ru']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Главная'; @@ -3533,7 +3533,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3546,7 +3546,7 @@ class AppLocalizationsRu extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 711bb933..441c7b58 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -9,7 +9,7 @@ class AppLocalizationsTr extends AppLocalizations { AppLocalizationsTr([String locale = 'tr']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Ana sayfa'; @@ -3500,7 +3500,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3513,7 +3513,7 @@ class AppLocalizationsTr extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index ab57bde4..398b6da1 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -9,7 +9,7 @@ class AppLocalizationsUk extends AppLocalizations { AppLocalizationsUk([String locale = 'uk']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Головна'; @@ -3533,7 +3533,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Завантаження SpotiFLAC v$version'; + return 'Завантаження SpotiFLAC Mobile v$version'; } @override @@ -3546,7 +3546,7 @@ class AppLocalizationsUk extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version завантажений. Натисніть щоб установити.'; + return 'SpotiFLAC Mobile v$version завантажений. Натисніть щоб установити.'; } @override diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 3a9f0211..bc431dce 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -9,7 +9,7 @@ class AppLocalizationsZh extends AppLocalizations { AppLocalizationsZh([String locale = 'zh']) : super(locale); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -3473,7 +3473,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -3486,7 +3486,7 @@ class AppLocalizationsZh extends AppLocalizations { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override @@ -3788,7 +3788,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { AppLocalizationsZhCn() : super('zh_CN'); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => '主页'; @@ -7170,7 +7170,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -7183,7 +7183,7 @@ class AppLocalizationsZhCn extends AppLocalizationsZh { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override @@ -7266,7 +7266,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { AppLocalizationsZhTw() : super('zh_TW'); @override - String get appName => 'SpotiFLAC'; + String get appName => 'SpotiFLAC Mobile'; @override String get navHome => 'Home'; @@ -10661,7 +10661,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String notifDownloadingUpdate(String version) { - return 'Downloading SpotiFLAC v$version'; + return 'Downloading SpotiFLAC Mobile v$version'; } @override @@ -10674,7 +10674,7 @@ class AppLocalizationsZhTw extends AppLocalizationsZh { @override String notifUpdateReadyBody(String version) { - return 'SpotiFLAC v$version downloaded. Tap to install.'; + return 'SpotiFLAC Mobile v$version downloaded. Tap to install.'; } @override diff --git a/lib/l10n/arb/app_de.arb b/lib/l10n/arb/app_de.arb index 85114818..703d18d3 100644 --- a/lib/l10n/arb/app_de.arb +++ b/lib/l10n/arb/app_de.arb @@ -1,7 +1,7 @@ { "@@locale": "de", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 1494cb2a..86ee9365 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -1,7 +1,7 @@ { "@@locale": "en", "@@last_modified": "2026-04-28", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4564,7 +4564,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4592,7 +4592,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_es.arb b/lib/l10n/arb/app_es.arb index a5294db3..a97975a0 100644 --- a/lib/l10n/arb/app_es.arb +++ b/lib/l10n/arb/app_es.arb @@ -1,7 +1,7 @@ { "@@locale": "es", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_es_ES.arb b/lib/l10n/arb/app_es_ES.arb index 32fc7fb8..97c1d443 100644 --- a/lib/l10n/arb/app_es_ES.arb +++ b/lib/l10n/arb/app_es_ES.arb @@ -1,7 +1,7 @@ { "@@locale": "es_ES", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_fr.arb b/lib/l10n/arb/app_fr.arb index f68dedc8..17a291f6 100644 --- a/lib/l10n/arb/app_fr.arb +++ b/lib/l10n/arb/app_fr.arb @@ -1,7 +1,7 @@ { "@@locale": "fr", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_hi.arb b/lib/l10n/arb/app_hi.arb index 2c18e2c2..aa0d129f 100644 --- a/lib/l10n/arb/app_hi.arb +++ b/lib/l10n/arb/app_hi.arb @@ -1,7 +1,7 @@ { "@@locale": "hi", "@@last_modified": "2026-01-16", - "appName": "SpotiFlac", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 73a7740f..9bfaf31f 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -1,7 +1,7 @@ { "@@locale": "id", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4481,7 +4481,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4509,7 +4509,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_ja.arb b/lib/l10n/arb/app_ja.arb index 7531f99b..833e7416 100644 --- a/lib/l10n/arb/app_ja.arb +++ b/lib/l10n/arb/app_ja.arb @@ -1,7 +1,7 @@ { "@@locale": "ja", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_ko.arb b/lib/l10n/arb/app_ko.arb index cf77be41..62d5e6eb 100644 --- a/lib/l10n/arb/app_ko.arb +++ b/lib/l10n/arb/app_ko.arb @@ -1,7 +1,7 @@ { "@@locale": "ko", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_nl.arb b/lib/l10n/arb/app_nl.arb index 91acf351..fd5cf244 100644 --- a/lib/l10n/arb/app_nl.arb +++ b/lib/l10n/arb/app_nl.arb @@ -1,7 +1,7 @@ { "@@locale": "nl", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_pt.arb b/lib/l10n/arb/app_pt.arb index bde8d0f1..bf10f942 100644 --- a/lib/l10n/arb/app_pt.arb +++ b/lib/l10n/arb/app_pt.arb @@ -1,7 +1,7 @@ { "@@locale": "pt", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_pt_PT.arb b/lib/l10n/arb/app_pt_PT.arb index 5443f666..eae06b54 100644 --- a/lib/l10n/arb/app_pt_PT.arb +++ b/lib/l10n/arb/app_pt_PT.arb @@ -1,7 +1,7 @@ { "@@locale": "pt_PT", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_ru.arb b/lib/l10n/arb/app_ru.arb index d3b4de6a..a43eb811 100644 --- a/lib/l10n/arb/app_ru.arb +++ b/lib/l10n/arb/app_ru.arb @@ -1,7 +1,7 @@ { "@@locale": "ru", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_tr.arb b/lib/l10n/arb/app_tr.arb index a4eed63e..8c74ea8f 100644 --- a/lib/l10n/arb/app_tr.arb +++ b/lib/l10n/arb/app_tr.arb @@ -1,7 +1,7 @@ { "@@locale": "tr", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4477,7 +4477,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4505,7 +4505,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_uk.arb b/lib/l10n/arb/app_uk.arb index d301f9e9..0c96855a 100644 --- a/lib/l10n/arb/app_uk.arb +++ b/lib/l10n/arb/app_uk.arb @@ -1,7 +1,7 @@ { "@@locale": "uk", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Завантаження SpotiFLAC v{version}", + "notifDownloadingUpdate": "Завантаження SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} завантажений. Натисніть щоб установити.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} завантажений. Натисніть щоб установити.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_zh.arb b/lib/l10n/arb/app_zh.arb index 3c25cbc7..68f560e0 100644 --- a/lib/l10n/arb/app_zh.arb +++ b/lib/l10n/arb/app_zh.arb @@ -1,7 +1,7 @@ { "@@locale": "zh", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_zh_CN.arb b/lib/l10n/arb/app_zh_CN.arb index 0d72d929..db4ede5d 100644 --- a/lib/l10n/arb/app_zh_CN.arb +++ b/lib/l10n/arb/app_zh_CN.arb @@ -1,7 +1,7 @@ { "@@locale": "zh_CN", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/l10n/arb/app_zh_TW.arb b/lib/l10n/arb/app_zh_TW.arb index cb6bead3..a98827bd 100644 --- a/lib/l10n/arb/app_zh_TW.arb +++ b/lib/l10n/arb/app_zh_TW.arb @@ -1,7 +1,7 @@ { "@@locale": "zh_TW", "@@last_modified": "2026-01-16", - "appName": "SpotiFLAC", + "appName": "SpotiFLAC Mobile", "@appName": { "description": "App name - DO NOT TRANSLATE" }, @@ -4473,7 +4473,7 @@ "@notifLibraryScanStopped": { "description": "Notification body when library scan is cancelled" }, - "notifDownloadingUpdate": "Downloading SpotiFLAC v{version}", + "notifDownloadingUpdate": "Downloading SpotiFLAC Mobile v{version}", "@notifDownloadingUpdate": { "description": "Notification title while downloading an app update", "placeholders": { @@ -4501,7 +4501,7 @@ "@notifUpdateReady": { "description": "Notification title when app update download is complete" }, - "notifUpdateReadyBody": "SpotiFLAC v{version} downloaded. Tap to install.", + "notifUpdateReadyBody": "SpotiFLAC Mobile v{version} downloaded. Tap to install.", "@notifUpdateReadyBody": { "description": "Notification body when app update is ready to install", "placeholders": { diff --git a/lib/providers/explore_provider.dart b/lib/providers/explore_provider.dart index 01997f3c..02340070 100644 --- a/lib/providers/explore_provider.dart +++ b/lib/providers/explore_provider.dart @@ -110,6 +110,7 @@ class ExploreState { final bool isLoading; final String? error; final String? greeting; + final String? providerId; final List sections; final DateTime? lastFetched; @@ -117,6 +118,7 @@ class ExploreState { this.isLoading = false, this.error, this.greeting, + this.providerId, this.sections = const [], this.lastFetched, }); @@ -127,6 +129,8 @@ class ExploreState { bool? isLoading, String? error, String? greeting, + String? providerId, + bool clearProviderId = false, List? sections, DateTime? lastFetched, }) { @@ -134,6 +138,7 @@ class ExploreState { isLoading: isLoading ?? this.isLoading, error: error, greeting: greeting ?? this.greeting, + providerId: clearProviderId ? null : (providerId ?? this.providerId), sections: sections ?? this.sections, lastFetched: lastFetched ?? this.lastFetched, ); @@ -189,14 +194,54 @@ List> _normalizeExploreSectionsPayload( return sections; } -List> _decodeExploreCacheSections(String rawCache) { - final decoded = jsonDecode(rawCache); - if (decoded is! Map) return const []; - return _normalizeExploreSectionsPayload(decoded['sections']); +List> _withDefaultExploreProviderId( + List> normalizedSections, + String providerId, +) { + final normalizedProviderId = providerId.trim(); + if (normalizedProviderId.isEmpty) return normalizedSections; + + return normalizedSections + .map((section) { + final rawItems = section['items']; + if (rawItems is! List) return section; + + return { + ...section, + 'items': rawItems + .map((rawItem) { + if (rawItem is! Map) return rawItem; + final item = Map.from(rawItem); + final itemProviderId = + item['provider_id']?.toString().trim() ?? ''; + if (itemProviderId.isEmpty) { + item['provider_id'] = normalizedProviderId; + } + return item; + }) + .toList(growable: false), + }; + }) + .toList(growable: false); } -String _encodeExploreCacheSections(List> sections) { - return jsonEncode({'sections': sections}); +Map _decodeExploreCache(String rawCache) { + final decoded = jsonDecode(rawCache); + if (decoded is! Map) { + return const {'provider_id': null, 'sections': >[]}; + } + + final providerId = decoded['provider_id']?.toString().trim(); + var sections = _normalizeExploreSectionsPayload(decoded['sections']); + if (providerId != null && providerId.isNotEmpty) { + sections = _withDefaultExploreProviderId(sections, providerId); + } + + return {'provider_id': providerId, 'sections': sections}; +} + +String _encodeExploreCache(Map cachePayload) { + return jsonEncode(cachePayload); } List _buildExploreSectionsFromNormalizedPayload( @@ -234,10 +279,24 @@ class ExploreNotifier extends Notifier { final cachedTs = prefs.getInt(_cacheTsKey); if (cached == null || cached.isEmpty) return; - final normalizedSections = await compute( - _decodeExploreCacheSections, - cached, - ); + final cachePayload = await compute(_decodeExploreCache, cached); + final providerId = cachePayload['provider_id']?.toString().trim(); + final rawSections = cachePayload['sections']; + var normalizedSections = rawSections is List + ? rawSections + .whereType>() + .map((section) => Map.from(section)) + .toList(growable: false) + : const >[]; + final resolvedProviderId = providerId?.isNotEmpty == true + ? providerId + : _resolveHomeFeedExtension()?.id; + if (resolvedProviderId != null && resolvedProviderId.isNotEmpty) { + normalizedSections = _withDefaultExploreProviderId( + normalizedSections, + resolvedProviderId, + ); + } final sections = _buildExploreSectionsFromNormalizedPayload( normalizedSections, ); @@ -251,23 +310,51 @@ class ExploreNotifier extends Notifier { _log.i('Restored ${sections.length} cached explore sections'); state = ExploreState( greeting: _getLocalGreeting(), + providerId: resolvedProviderId, sections: sections, lastFetched: lastFetched, ); } catch (e) { _log.w('Failed to restore explore cache: $e'); + try { + final prefs = await SharedPreferences.getInstance(); + await prefs.remove(_cacheKey); + await prefs.remove(_cacheTsKey); + _log.d('Removed invalid explore cache'); + } catch (clearError) { + _log.w('Failed to remove invalid explore cache: $clearError'); + } } } + Extension? _resolveHomeFeedExtension() { + final settings = ref.read(settingsProvider); + final preferredId = settings.homeFeedProvider; + final enabledHomeFeedExtensions = ref + .read(extensionProvider) + .extensions + .where((extension) => extension.enabled && extension.hasHomeFeed) + .toList(growable: false); + + if (preferredId != null && preferredId.isNotEmpty) { + return enabledHomeFeedExtensions + .where((extension) => extension.id == preferredId) + .firstOrNull; + } + + return enabledHomeFeedExtensions.firstOrNull; + } + Future _saveToCache( List> normalizedSections, + String providerId, ) async { try { final prefs = await SharedPreferences.getInstance(); - final encoded = await compute( - _encodeExploreCacheSections, - normalizedSections, - ); + final encoded = await compute(_encodeExploreCache, { + 'provider_id': providerId, + 'sections': normalizedSections, + }); await prefs.setString(_cacheKey, encoded); await prefs.setInt(_cacheTsKey, DateTime.now().millisecondsSinceEpoch); _log.d('Saved ${normalizedSections.length} explore sections to cache'); @@ -313,24 +400,7 @@ class ExploreNotifier extends Notifier { 'Extensions count: ${extState.extensions.length}, preferred home feed: $preferredId', ); - Extension? targetExt; - for (final extension in extState.extensions) { - if (!extension.enabled || !extension.hasHomeFeed) { - continue; - } - if (preferredId != null && - preferredId.isNotEmpty && - extension.id == preferredId) { - targetExt = extension; - break; - } - if (targetExt == null || extension.id == 'spotify-web') { - targetExt = extension; - if (preferredId == null && extension.id == 'spotify-web') { - break; - } - } - } + final targetExt = _resolveHomeFeedExtension(); if (targetExt == null) { _log.w('No extension with homeFeed capability found'); @@ -367,10 +437,14 @@ class ExploreNotifier extends Notifier { final greeting = result['greeting'] as String?; final sectionsData = result['sections'] as List? ?? []; - final normalizedSections = await compute( + final normalizedSectionsWithoutProvider = await compute( _normalizeExploreSectionsPayload, sectionsData, ); + final normalizedSections = _withDefaultExploreProviderId( + normalizedSectionsWithoutProvider, + targetExt.id, + ); if (requestId != _homeFeedRequestId) return; final sections = _buildExploreSectionsFromNormalizedPayload( normalizedSections, @@ -391,11 +465,12 @@ class ExploreNotifier extends Notifier { state = ExploreState( isLoading: false, greeting: localGreeting, + providerId: targetExt.id, sections: sections, lastFetched: DateTime.now(), ); - _saveToCache(normalizedSections); + _saveToCache(normalizedSections, targetExt.id); } catch (e, stack) { _log.e('Error fetching home feed: $e', e, stack); if (requestId != _homeFeedRequestId) return; diff --git a/lib/providers/extension_provider.dart b/lib/providers/extension_provider.dart index 05d418a3..03c83644 100644 --- a/lib/providers/extension_provider.dart +++ b/lib/providers/extension_provider.dart @@ -24,6 +24,30 @@ bool _stringListEquals(List a, List b) { return true; } +List? _tryDecodeStringListPreference(String rawJson, String key) { + try { + final decoded = jsonDecode(rawJson); + if (decoded is! List) { + throw const FormatException('expected a JSON list'); + } + + final values = []; + for (final item in decoded) { + if (item is! String) { + throw const FormatException('expected string entries'); + } + final trimmed = item.trim(); + if (trimmed.isNotEmpty) { + values.add(trimmed); + } + } + return values; + } catch (e) { + _log.w('Ignoring invalid $key preference: $e'); + return null; + } +} + class BuiltInProviderSpec { final String id; final String displayName; @@ -1630,15 +1654,27 @@ class ExtensionNotifier extends Notifier { List priority; if (savedJson != null) { - final saved = jsonDecode(savedJson) as List; - priority = saved.map((e) => e as String).toList(); - priority = _sanitizeDownloadProviderPriority(priority); - _log.d('Loaded provider priority from prefs: $priority'); - await prefs.setString(_providerPriorityKey, jsonEncode(priority)); - await PlatformBridge.setProviderPriority(priority); + final saved = _tryDecodeStringListPreference( + savedJson, + _providerPriorityKey, + ); + if (saved != null) { + priority = _sanitizeDownloadProviderPriority(saved); + _log.d('Loaded provider priority from prefs: $priority'); + await prefs.setString(_providerPriorityKey, jsonEncode(priority)); + await PlatformBridge.setProviderPriority(priority); + } else { + await prefs.remove(_providerPriorityKey); + priority = await PlatformBridge.getProviderPriority(); + priority = _sanitizeDownloadProviderPriority(priority); + await prefs.setString(_providerPriorityKey, jsonEncode(priority)); + await PlatformBridge.setProviderPriority(priority); + _log.d('Recovered provider priority from defaults: $priority'); + } } else { priority = await PlatformBridge.getProviderPriority(); priority = _sanitizeDownloadProviderPriority(priority); + await prefs.setString(_providerPriorityKey, jsonEncode(priority)); await PlatformBridge.setProviderPriority(priority); _log.d('Using default provider priority: $priority'); } @@ -1691,18 +1727,34 @@ class ExtensionNotifier extends Notifier { List priority; if (savedJson != null) { - final saved = jsonDecode(savedJson) as List; - priority = _sanitizeMetadataProviderPriority( - _replaceRetiredBuiltInMetadataProviders( - saved.map((e) => e as String).toList(), - ), - ); - _log.d('Loaded metadata provider priority from prefs: $priority'); - await prefs.setString( + final saved = _tryDecodeStringListPreference( + savedJson, _metadataProviderPriorityKey, - jsonEncode(priority), ); - await PlatformBridge.setMetadataProviderPriority(priority); + if (saved != null) { + priority = _sanitizeMetadataProviderPriority( + _replaceRetiredBuiltInMetadataProviders(saved), + ); + _log.d('Loaded metadata provider priority from prefs: $priority'); + await prefs.setString( + _metadataProviderPriorityKey, + jsonEncode(priority), + ); + await PlatformBridge.setMetadataProviderPriority(priority); + } else { + await prefs.remove(_metadataProviderPriorityKey); + final backendPriority = + await PlatformBridge.getMetadataProviderPriority(); + priority = _sanitizeMetadataProviderPriority(backendPriority); + await prefs.setString( + _metadataProviderPriorityKey, + jsonEncode(priority), + ); + await PlatformBridge.setMetadataProviderPriority(priority); + _log.d( + 'Recovered metadata provider priority from defaults: $priority', + ); + } } else { final backendPriority = await PlatformBridge.getMetadataProviderPriority(); diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index e6ca78d0..7b58818d 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -11,6 +11,7 @@ import 'package:spotiflac_android/utils/file_access.dart'; import 'package:spotiflac_android/utils/logger.dart'; const _settingsKey = 'app_settings'; +const _settingsCorruptBackupKey = 'app_settings_corrupt_backup'; const _migrationVersionKey = 'settings_migration_version'; const _currentMigrationVersion = 11; const _spotifyClientSecretKey = 'spotify_client_secret'; @@ -41,40 +42,56 @@ class SettingsNotifier extends Notifier { Future _loadSettings() async { final prefs = await _prefs; - final json = prefs.getString(_settingsKey); - if (json != null) { - final loaded = AppSettings.fromJson( - Map.from(jsonDecode(json) as Map), - ); - final sanitizedDownloadFallbackExtensionIds = - _sanitizeDownloadFallbackExtensionIds( - loaded.downloadFallbackExtensionIds, - ); - final sanitizedDefaultSearchTab = _normalizeDefaultSearchTab( - loaded.defaultSearchTab, - ); - final sanitizedDefaultService = _sanitizeRetiredBuiltInProviderId( - loaded.defaultService, - ); - final sanitizedSearchProvider = _sanitizeRetiredBuiltInProviderId( - loaded.searchProvider, - ); - state = loaded.copyWith( - useExtensionProviders: true, - downloadFallbackExtensionIds: sanitizedDownloadFallbackExtensionIds, - clearDownloadFallbackExtensionIds: - loaded.downloadFallbackExtensionIds != null && - sanitizedDownloadFallbackExtensionIds == null, - defaultSearchTab: sanitizedDefaultSearchTab, - defaultService: sanitizedDefaultService ?? '', - searchProvider: sanitizedSearchProvider, - clearSearchProvider: - loaded.searchProvider != null && sanitizedSearchProvider == null, - ); + final rawSettings = prefs.getString(_settingsKey); + if (rawSettings != null) { + AppSettings? loaded; + try { + final decoded = jsonDecode(rawSettings); + if (decoded is! Map) { + throw const FormatException('settings root must be a JSON object'); + } + loaded = AppSettings.fromJson(Map.from(decoded)); + } catch (e, stack) { + _log.e('Failed to load settings, resetting to defaults: $e', e, stack); + try { + await prefs.setString(_settingsCorruptBackupKey, rawSettings); + await prefs.remove(_settingsKey); + } catch (backupError) { + _log.w('Failed to backup corrupt settings: $backupError'); + } + } - await _runMigrations(prefs); - await _normalizeIosDownloadDirectoryIfNeeded(); - await _normalizeSongLinkRegionIfNeeded(); + if (loaded != null) { + final sanitizedDownloadFallbackExtensionIds = + _sanitizeDownloadFallbackExtensionIds( + loaded.downloadFallbackExtensionIds, + ); + final sanitizedDefaultSearchTab = _normalizeDefaultSearchTab( + loaded.defaultSearchTab, + ); + final sanitizedDefaultService = _sanitizeRetiredBuiltInProviderId( + loaded.defaultService, + ); + final sanitizedSearchProvider = _sanitizeRetiredBuiltInProviderId( + loaded.searchProvider, + ); + state = loaded.copyWith( + useExtensionProviders: true, + downloadFallbackExtensionIds: sanitizedDownloadFallbackExtensionIds, + clearDownloadFallbackExtensionIds: + loaded.downloadFallbackExtensionIds != null && + sanitizedDownloadFallbackExtensionIds == null, + defaultSearchTab: sanitizedDefaultSearchTab, + defaultService: sanitizedDefaultService ?? '', + searchProvider: sanitizedSearchProvider, + clearSearchProvider: + loaded.searchProvider != null && sanitizedSearchProvider == null, + ); + + await _runMigrations(prefs); + await _normalizeIosDownloadDirectoryIfNeeded(); + await _normalizeSongLinkRegionIfNeeded(); + } } await _cleanupRetiredSpotifySettings(); diff --git a/lib/providers/track_provider.dart b/lib/providers/track_provider.dart index 114c6a78..3a1c54a5 100644 --- a/lib/providers/track_provider.dart +++ b/lib/providers/track_provider.dart @@ -449,7 +449,7 @@ class TrackNotifier extends Notifier { albumName: albumInfo['name'] as String?, coverUrl: normalizeRemoteHttpUrl(albumInfo['images']?.toString()), ); - _preWarmCacheForTracks(tracks); + _preWarmCacheForTracks(tracks, service: providerId); return; case 'playlist': final playlistInfo = metadata['playlist_info'] as Map; @@ -469,7 +469,7 @@ class TrackNotifier extends Notifier { playlistName: playlistName, coverUrl: coverUrl, ); - _preWarmCacheForTracks(tracks); + _preWarmCacheForTracks(tracks, service: providerId); return; case 'artist': final artistInfo = metadata['artist_info'] as Map; @@ -1054,7 +1054,7 @@ class TrackNotifier extends Notifier { ); } - void _preWarmCacheForTracks(List tracks) { + void _preWarmCacheForTracks(List tracks, {String? service}) { if (tracks.isEmpty) return; final cacheRequests = >[]; for (final track in tracks) { @@ -1062,12 +1062,16 @@ class TrackNotifier extends Notifier { if (isrc == null || isrc.isEmpty) { continue; } + final effectiveService = + (track.source?.trim().isNotEmpty == true ? track.source : service) + ?.trim(); cacheRequests.add({ 'isrc': isrc, 'track_name': track.name, 'artist_name': track.artistName, 'spotify_id': track.id, - 'service': 'tidal', + if (effectiveService != null && effectiveService.isNotEmpty) + 'service': effectiveService, }); if (cacheRequests.length >= _maxPreWarmTracksPerRequest) { break; diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index db340e7b..bd5ca57d 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -1896,14 +1896,38 @@ class _HomeTabState extends ConsumerState } } + String? _providerIdForExploreItem(ExploreItem item) { + final itemProviderId = item.providerId?.trim(); + if (itemProviderId != null && itemProviderId.isNotEmpty) { + return itemProviderId; + } + + final feedProviderId = ref.read(exploreProvider).providerId?.trim(); + if (feedProviderId != null && feedProviderId.isNotEmpty) { + return feedProviderId; + } + + return null; + } + + void _showMissingExploreProviderMessage() { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(context.l10n.extensionsNoHomeFeedExtensions)), + ); + } + void _navigateToExploreItem(ExploreItem item) async { - final extensionId = item.providerId ?? 'spotify-web'; + final extensionId = _providerIdForExploreItem(item); switch (item.type) { case 'track': _showTrackBottomSheet(item); return; case 'album': + if (extensionId == null) { + _showMissingExploreProviderMessage(); + return; + } Navigator.push( context, MaterialPageRoute( @@ -1917,6 +1941,10 @@ class _HomeTabState extends ConsumerState ); return; case 'playlist': + if (extensionId == null) { + _showMissingExploreProviderMessage(); + return; + } Navigator.push( context, MaterialPageRoute( @@ -1930,6 +1958,10 @@ class _HomeTabState extends ConsumerState ); return; case 'artist': + if (extensionId == null) { + _showMissingExploreProviderMessage(); + return; + } Navigator.push( context, MaterialPageRoute( @@ -2064,7 +2096,7 @@ class _HomeTabState extends ConsumerState isrc: null, releaseDate: item.releaseDate, coverUrl: item.coverUrl, - source: item.providerId ?? 'spotify-web', + source: _providerIdForExploreItem(item), ); if (settings.askQualityBeforeDownload) { @@ -2105,11 +2137,17 @@ class _HomeTabState extends ConsumerState Future _navigateToTrackAlbum(ExploreItem item) async { if (item.albumId != null && item.albumId!.isNotEmpty) { + final extensionId = _providerIdForExploreItem(item); + if (extensionId == null) { + _showMissingExploreProviderMessage(); + return; + } + Navigator.push( context, MaterialPageRoute( builder: (context) => ExtensionAlbumScreen( - extensionId: item.providerId ?? 'spotify-web', + extensionId: extensionId, albumId: item.albumId!, albumName: item.albumName ?? 'Album', coverUrl: item.coverUrl, diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 4ac3c392..9b3e8822 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -3,6 +3,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:permission_handler/permission_handler.dart'; +import 'package:spotiflac_android/constants/app_info.dart'; import 'package:spotiflac_android/l10n/app_localizations.dart'; class NotificationService { @@ -547,7 +548,7 @@ class NotificationService { id: updateDownloadId, title: _l10n?.notifDownloadingUpdate(version) ?? - 'Downloading SpotiFLAC v$version', + 'Downloading ${AppInfo.appName} v$version', body: _l10n?.notifUpdateProgress(receivedMB, totalMB, percentage) ?? '$receivedMB / $totalMB MB • $percentage%', @@ -585,7 +586,7 @@ class NotificationService { title: _l10n?.notifUpdateReady ?? 'Update Ready', body: _l10n?.notifUpdateReadyBody(version) ?? - 'SpotiFLAC v$version downloaded. Tap to install.', + '${AppInfo.appName} v$version downloaded. Tap to install.', details: details, ); } diff --git a/lib/utils/clickable_metadata.dart b/lib/utils/clickable_metadata.dart index 5afaa1ec..d561ef83 100644 --- a/lib/utils/clickable_metadata.dart +++ b/lib/utils/clickable_metadata.dart @@ -1,8 +1,10 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; import 'package:spotiflac_android/services/platform_bridge.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; +import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/screens/artist_screen.dart'; import 'package:spotiflac_android/screens/album_screen.dart'; import 'package:spotiflac_android/screens/home_tab.dart' @@ -12,20 +14,151 @@ import 'package:spotiflac_android/utils/artist_utils.dart'; import 'package:spotiflac_android/utils/logger.dart'; final _log = AppLogger('ClickableMetadata'); -const _deezerExtensionId = 'deezer'; -Future>> _searchDeezerExtension( +class _MetadataSearchResult { + final String providerId; + final List> items; + + const _MetadataSearchResult({required this.providerId, required this.items}); +} + +Future<_MetadataSearchResult?> _searchMetadataProviders( + BuildContext context, String query, { required String filter, int limit = 5, -}) { + String? sourceProviderId, +}) async { + final providerIds = _metadataSearchProviderCandidates( + context, + sourceProviderId: sourceProviderId, + ); + + for (final providerId in providerIds) { + try { + final items = await _searchMetadataProvider( + providerId, + query, + filter: filter, + limit: limit, + ); + if (items.isNotEmpty) { + return _MetadataSearchResult(providerId: providerId, items: items); + } + } catch (e) { + _log.w( + 'Metadata lookup failed for provider "$providerId", filter=$filter: $e', + ); + } + } + + return null; +} + +Future>> _searchMetadataProvider( + String providerId, + String query, { + required String filter, + required int limit, +}) async { + if (isBuiltInSearchProvider(providerId)) { + final result = await PlatformBridge.searchProviderAll( + providerId, + query, + trackLimit: 0, + artistLimit: filter == 'artist' ? limit : 0, + filter: filter, + ); + return _extractSearchItems(result, filter); + } + return PlatformBridge.customSearchWithExtension( - _deezerExtensionId, + providerId, query, options: {'filter': filter, 'limit': limit}, ); } +List> _extractSearchItems( + Map result, + String filter, +) { + final key = switch (filter) { + 'artist' => 'artists', + 'album' => 'albums', + _ => '${filter}s', + }; + final items = result[key]; + if (items is! List) return const []; + + return items + .whereType>() + .map((item) => Map.from(item)) + .toList(growable: false); +} + +List _metadataSearchProviderCandidates( + BuildContext context, { + String? sourceProviderId, +}) { + final container = ProviderScope.containerOf(context, listen: false); + final extensionState = container.read(extensionProvider); + final settings = container.read(settingsProvider); + final extensionNotifier = container.read(extensionProvider.notifier); + final candidates = []; + + void addProvider(String? providerId) { + final normalized = providerId?.trim(); + if (normalized == null || + normalized.isEmpty || + candidates.contains(normalized) || + !_canSearchMetadataProvider(normalized, extensionState)) { + return; + } + candidates.add(normalized); + } + + addProvider(sourceProviderId); + addProvider(settings.searchProvider); + + for (final providerId in extensionState.metadataProviderPriority) { + addProvider(providerId); + } + for (final providerId in extensionNotifier.getAllMetadataProviders()) { + addProvider(providerId); + } + + final searchExtensions = extensionState.extensions + .where((ext) => ext.enabled && ext.hasCustomSearch) + .toList(growable: false); + for (final extension in searchExtensions.where( + (ext) => ext.searchBehavior?.primary == true, + )) { + addProvider(extension.id); + } + for (final extension in searchExtensions.where( + (ext) => ext.searchBehavior?.primary != true, + )) { + addProvider(extension.id); + } + + for (final providerId in builtInSearchProviderIds) { + addProvider(providerId); + } + + return candidates; +} + +bool _canSearchMetadataProvider( + String providerId, + ExtensionState extensionState, +) { + if (isBuiltInSearchProvider(providerId)) return true; + return extensionState.extensions.any( + (ext) => ext.enabled && ext.hasCustomSearch && ext.id == providerId, + ); +} + Future navigateToArtist( BuildContext context, { required String artistName, @@ -54,14 +187,17 @@ Future navigateToArtist( _showLoadingSnackBar(context, context.l10n.clickableLookingUpArtist); try { - final artistList = await _searchDeezerExtension( + final searchResult = await _searchMetadataProviders( + context, artistName, filter: 'artist', limit: 3, + sourceProviderId: extensionId, ); if (!context.mounted) return; ScaffoldMessenger.of(context).hideCurrentSnackBar(); + final artistList = searchResult?.items ?? const >[]; if (artistList.isEmpty) { _showUnavailable(context, context.l10n.trackArtist); return; @@ -81,6 +217,10 @@ Future navigateToArtist( final resolvedId = bestMatch['id'] as String? ?? ''; final resolvedName = bestMatch['name'] as String? ?? artistName; final resolvedImage = bestMatch['images'] as String?; + final resolvedProviderId = _resolveResultProviderId( + bestMatch, + searchResult?.providerId, + ); if (resolvedId.isEmpty) { _showUnavailable(context, context.l10n.trackArtist); @@ -93,7 +233,7 @@ Future navigateToArtist( artistId: resolvedId, artistName: resolvedName, coverUrl: resolvedImage ?? coverUrl, - extensionId: _deezerExtensionId, + extensionId: resolvedProviderId, ); } catch (e) { _log.e('Failed to look up artist "$artistName": $e', e); @@ -113,10 +253,7 @@ Future navigateToAlbum( }) async { if (albumName.isEmpty) return; - if (albumId != null && - albumId.isNotEmpty && - albumId != 'unknown' && - albumId != 'deezer:unknown') { + if (albumId != null && albumId.isNotEmpty && !_isUnknownResourceId(albumId)) { _pushAlbumScreen( context, albumId: albumId, @@ -127,25 +264,23 @@ Future navigateToAlbum( return; } - if (extensionId != null) { - _showUnavailable(context, 'Album'); - return; - } - _showLoadingSnackBar(context, 'Looking up album...'); try { final query = artistName != null && artistName.isNotEmpty ? '$albumName $artistName' : albumName; - final albumList = await _searchDeezerExtension( + final searchResult = await _searchMetadataProviders( + context, query, filter: 'album', limit: 5, + sourceProviderId: extensionId, ); if (!context.mounted) return; ScaffoldMessenger.of(context).hideCurrentSnackBar(); + final albumList = searchResult?.items ?? const >[]; if (albumList.isEmpty) { _showUnavailable(context, 'Album'); return; @@ -165,6 +300,10 @@ Future navigateToAlbum( final resolvedId = bestMatch['id'] as String? ?? ''; final resolvedName = bestMatch['name'] as String? ?? albumName; final resolvedImage = bestMatch['images'] as String?; + final resolvedProviderId = _resolveResultProviderId( + bestMatch, + searchResult?.providerId, + ); if (resolvedId.isEmpty) { _showUnavailable(context, 'Album'); @@ -177,7 +316,7 @@ Future navigateToAlbum( albumId: resolvedId, albumName: resolvedName, coverUrl: resolvedImage ?? coverUrl, - extensionId: _deezerExtensionId, + extensionId: resolvedProviderId, ); } catch (e) { _log.e('Failed to look up album "$albumName": $e', e); @@ -194,11 +333,15 @@ void _pushArtistScreen( String? coverUrl, String? extensionId, }) { + final isExtension = + extensionId != null && !isBuiltInMetadataProvider(extensionId); + final resolvedProviderId = extensionId; + _pushViaPreferredNavigator( context, - (context) => extensionId != null + (context) => isExtension && resolvedProviderId != null ? ExtensionArtistScreen( - extensionId: extensionId, + extensionId: resolvedProviderId, artistId: artistId, artistName: artistName, coverUrl: coverUrl, @@ -207,6 +350,7 @@ void _pushArtistScreen( artistId: artistId, artistName: artistName, coverUrl: coverUrl, + extensionId: resolvedProviderId, ), ); } @@ -235,6 +379,7 @@ void _pushAlbumScreen( albumId: albumId, albumName: albumName, coverUrl: coverUrl, + extensionId: resolvedExtensionId, tracks: const [], ), ); @@ -289,9 +434,7 @@ void _showLoadingSnackBar(BuildContext context, String message) { } void _showUnavailable(BuildContext context, String type) { - ScaffoldMessenger.of( - context, - ).showSnackBar( + ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text(context.l10n.clickableInformationUnavailable(type))), ); } @@ -504,21 +647,49 @@ List _parseArtistIds(String? rawArtistIds) { String? _normalizeArtistId(String? artistId) { final id = artistId?.trim(); - if (id == null || id.isEmpty || id == 'unknown' || id == 'deezer:unknown') { + if (id == null || _isUnknownResourceId(id)) { return null; } return id; } +bool _isUnknownResourceId(String id) { + final normalized = id.trim().toLowerCase(); + return normalized.isEmpty || + normalized == 'unknown' || + normalized.endsWith(':unknown'); +} + +String? _resolveResultProviderId( + Map result, + String? fallbackProviderId, +) { + final providerId = result['provider_id']?.toString().trim(); + if (providerId != null && providerId.isNotEmpty) return providerId; + final source = result['source']?.toString().trim(); + if (source != null && source.isNotEmpty) return source; + final fallback = fallbackProviderId?.trim(); + return fallback != null && fallback.isNotEmpty ? fallback : null; +} + bool _canNavigateArtistDirectly({ required String artistId, required String? extensionId, }) { if (extensionId != null) return true; - if (artistId.startsWith('deezer:')) return true; + final providerPrefix = _resourceProviderPrefix(artistId); + if (providerPrefix != null && isBuiltInMetadataProvider(providerPrefix)) { + return true; + } return _spotifyArtistIdPattern.hasMatch(artistId); } +String? _resourceProviderPrefix(String resourceId) { + final colonIndex = resourceId.indexOf(':'); + if (colonIndex <= 0) return null; + return resourceId.substring(0, colonIndex).trim(); +} + final RegExp _spotifyArtistIdPattern = RegExp(r'^[A-Za-z0-9]{22}$'); class ClickableAlbumName extends StatelessWidget {