feat: add built-in Tidal/Qobuz search with recommended service picker

- Add SearchAll() for Tidal and Qobuz in Go backend (tracks, artists, albums)
- Add searchTidalAll/searchQobuzAll platform routing for Android and iOS
- Add Tidal/Qobuz options to search provider dropdown in home tab
- Show (Recommended) label and auto-select service in download picker
This commit is contained in:
zarzet committed 2026-03-25 13:52:57 +07:00
1 parent 98fdc0ed7c
commit 4f365ca7fe
20 files changed
+720 -97

No files matched your search

@@ -1191,7 +1191,7 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
Future<void> _pickDirectory(BuildContext context, WidgetRef ref) async {
if (Platform.isIOS) {
_showIOSDirectoryOptions(context, ref);
} else {
} else if (Platform.isAndroid) {
_showAndroidDirectoryOptions(context, ref);
}
}
@@ -1626,9 +1626,9 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Text(
context.l10n.downloadLossy320Format,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
fontWeight: FontWeight.bold,
),
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
),
Padding(
+37 -21
View File
@@ -73,11 +73,13 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
} else if (Platform.isIOS) {
// iOS doesn't need explicit storage permission for app documents
setState(() => _hasStoragePermission = true);
} else {
setState(() => _hasStoragePermission = true);
}
}
Future<bool> _requestStoragePermission() async {
if (Platform.isIOS) return true;
if (!Platform.isAndroid) return true;
// SAF on Android 10+ doesn't need MANAGE_EXTERNAL_STORAGE
if (_androidSdkVersion >= 29) return true;
@@ -135,8 +137,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
if (Platform.isIOS) {
// On iOS, create a security-scoped bookmark so we can access
// this folder across app restarts and from the Go backend.
final bookmark =
await PlatformBridge.createIosBookmarkFromPath(result);
final bookmark = await PlatformBridge.createIosBookmarkFromPath(
result,
);
if (bookmark != null && bookmark.isNotEmpty) {
ref
.read(settingsProvider.notifier)
@@ -182,11 +185,13 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
return;
}
await ref.read(localLibraryProvider.notifier).startScan(
libraryPath,
forceFullScan: forceFullScan,
iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null,
);
await ref
.read(localLibraryProvider.notifier)
.startScan(
libraryPath,
forceFullScan: forceFullScan,
iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null,
);
}
Future<void> _cancelScan() async {
@@ -272,10 +277,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
padding: const EdgeInsets.fromLTRB(24, 24, 24, 8),
child: Text(
context.l10n.libraryAutoScan,
style: Theme.of(context)
.textTheme
.titleLarge
?.copyWith(fontWeight: FontWeight.bold),
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
),
Padding(
@@ -293,7 +297,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
selected: current == 'off',
colorScheme: colorScheme,
onTap: () {
ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('off');
ref
.read(settingsProvider.notifier)
.setLocalLibraryAutoScan('off');
Navigator.pop(context);
},
),
@@ -303,7 +309,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
selected: current == 'on_open',
colorScheme: colorScheme,
onTap: () {
ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('on_open');
ref
.read(settingsProvider.notifier)
.setLocalLibraryAutoScan('on_open');
Navigator.pop(context);
},
),
@@ -313,7 +321,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
selected: current == 'daily',
colorScheme: colorScheme,
onTap: () {
ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('daily');
ref
.read(settingsProvider.notifier)
.setLocalLibraryAutoScan('daily');
Navigator.pop(context);
},
),
@@ -323,7 +333,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
selected: current == 'weekly',
colorScheme: colorScheme,
onTap: () {
ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('weekly');
ref
.read(settingsProvider.notifier)
.setLocalLibraryAutoScan('weekly');
Navigator.pop(context);
},
),
@@ -443,9 +455,15 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
child: SettingsItem(
icon: Icons.autorenew_rounded,
title: context.l10n.libraryAutoScan,
subtitle: _getAutoScanLabel(context, settings.localLibraryAutoScan),
subtitle: _getAutoScanLabel(
context,
settings.localLibraryAutoScan,
),
onTap: settings.localLibraryEnabled
? () => _showAutoScanPicker(context, settings.localLibraryAutoScan)
? () => _showAutoScanPicker(
context,
settings.localLibraryAutoScan,
)
: null,
showDivider: false,
),
@@ -950,9 +968,7 @@ class _AutoScanOption extends StatelessWidget {
return ListTile(
leading: Icon(icon),
title: Text(title),
trailing: selected
? Icon(Icons.check, color: colorScheme.primary)
: null,
trailing: selected ? Icon(Icons.check, color: colorScheme.primary) : null,
onTap: onTap,
);
}