From 6710f90e1e7736b4530f6cc510269d7fcea14cfa Mon Sep 17 00:00:00 2001 From: zarzet Date: Mon, 16 Mar 2026 20:28:45 +0700 Subject: [PATCH] feat: add auto-scan option for local library Add a new 'Auto Scan' setting under Local Library with four modes: off, every app open (10min cooldown), daily, and weekly. The app uses WidgetsBindingObserver to trigger incremental scans on launch and when resuming from background, respecting the configured cooldown based on the last scan timestamp. --- lib/l10n/app_localizations.dart | 36 +++++ lib/l10n/app_localizations_de.dart | 19 +++ lib/l10n/app_localizations_en.dart | 19 +++ lib/l10n/app_localizations_es.dart | 19 +++ lib/l10n/app_localizations_fr.dart | 19 +++ lib/l10n/app_localizations_hi.dart | 19 +++ lib/l10n/app_localizations_id.dart | 19 +++ lib/l10n/app_localizations_ja.dart | 19 +++ lib/l10n/app_localizations_ko.dart | 19 +++ lib/l10n/app_localizations_nl.dart | 19 +++ lib/l10n/app_localizations_pt.dart | 19 +++ lib/l10n/app_localizations_ru.dart | 19 +++ lib/l10n/app_localizations_tr.dart | 19 +++ lib/l10n/app_localizations_zh.dart | 19 +++ lib/l10n/arb/app_en.arb | 24 ++++ lib/main.dart | 75 +++++++++- lib/models/settings.dart | 6 + lib/models/settings.g.dart | 2 + lib/providers/settings_provider.dart | 5 + .../settings/library_settings_page.dart | 134 +++++++++++++++++- 20 files changed, 526 insertions(+), 3 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 15451886..0ea69f2b 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3106,6 +3106,42 @@ abstract class AppLocalizations { /// **'Show when searching for existing tracks'** String get libraryShowDuplicateIndicatorSubtitle; + /// Setting for automatic library scanning + /// + /// In en, this message translates to: + /// **'Auto Scan'** + String get libraryAutoScan; + + /// Subtitle for auto scan setting + /// + /// In en, this message translates to: + /// **'Automatically scan your library for new files'** + String get libraryAutoScanSubtitle; + + /// Auto scan disabled + /// + /// In en, this message translates to: + /// **'Off'** + String get libraryAutoScanOff; + + /// Auto scan when app opens + /// + /// In en, this message translates to: + /// **'Every app open'** + String get libraryAutoScanOnOpen; + + /// Auto scan once per day + /// + /// In en, this message translates to: + /// **'Daily'** + String get libraryAutoScanDaily; + + /// Auto scan once per week + /// + /// In en, this message translates to: + /// **'Weekly'** + String get libraryAutoScanWeekly; + /// Section header for library actions /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index f0137de1..7ad45e21 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1719,6 +1719,25 @@ class AppLocalizationsDe extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Bei der Suche nach vorhandenen Titeln anzeigen'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Aktionen'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 041c5bd1..13208d5b 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsEn extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 99849885..113c08f1 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsEs extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index cec6eae9..6521f755 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1697,6 +1697,25 @@ class AppLocalizationsFr extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 9a9ad518..04227142 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsHi extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 902d49cd..b23768e3 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -1702,6 +1702,25 @@ class AppLocalizationsId extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 31270670..caf7d16e 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1682,6 +1682,25 @@ class AppLocalizationsJa extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'アクション'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 76f1b737..1cac05e7 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1675,6 +1675,25 @@ class AppLocalizationsKo extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index d509e1e7..ac2c8b57 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsNl extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index ef87eca7..3c5c8896 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsPt extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 8bbb8a74..31a1a1dc 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1731,6 +1731,25 @@ class AppLocalizationsRu extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Показать при поиске существующих треков'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Действия'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index bbf6ef14..518bcf77 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1707,6 +1707,25 @@ class AppLocalizationsTr extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 465f196c..d36cf154 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -1695,6 +1695,25 @@ class AppLocalizationsZh extends AppLocalizations { String get libraryShowDuplicateIndicatorSubtitle => 'Show when searching for existing tracks'; + @override + String get libraryAutoScan => 'Auto Scan'; + + @override + String get libraryAutoScanSubtitle => + 'Automatically scan your library for new files'; + + @override + String get libraryAutoScanOff => 'Off'; + + @override + String get libraryAutoScanOnOpen => 'Every app open'; + + @override + String get libraryAutoScanDaily => 'Daily'; + + @override + String get libraryAutoScanWeekly => 'Weekly'; + @override String get libraryActions => 'Actions'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7f4e1acb..7055840d 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2242,6 +2242,30 @@ "@libraryShowDuplicateIndicatorSubtitle": { "description": "Subtitle for duplicate indicator toggle" }, + "libraryAutoScan": "Auto Scan", + "@libraryAutoScan": { + "description": "Setting for automatic library scanning" + }, + "libraryAutoScanSubtitle": "Automatically scan your library for new files", + "@libraryAutoScanSubtitle": { + "description": "Subtitle for auto scan setting" + }, + "libraryAutoScanOff": "Off", + "@libraryAutoScanOff": { + "description": "Auto scan disabled" + }, + "libraryAutoScanOnOpen": "Every app open", + "@libraryAutoScanOnOpen": { + "description": "Auto scan when app opens" + }, + "libraryAutoScanDaily": "Daily", + "@libraryAutoScanDaily": { + "description": "Auto scan once per day" + }, + "libraryAutoScanWeekly": "Weekly", + "@libraryAutoScanWeekly": { + "description": "Auto scan once per week" + }, "libraryActions": "Actions", "@libraryActions": { "description": "Section header for library actions" diff --git a/lib/main.dart b/lib/main.dart index 63d74d3a..7ead4a81 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -4,6 +4,7 @@ import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:spotiflac_android/app.dart'; import 'package:spotiflac_android/providers/download_queue_provider.dart'; import 'package:spotiflac_android/providers/extension_provider.dart'; @@ -90,16 +91,21 @@ class _EagerInitialization extends ConsumerStatefulWidget { _EagerInitializationState(); } -class _EagerInitializationState extends ConsumerState<_EagerInitialization> { +class _EagerInitializationState extends ConsumerState<_EagerInitialization> + with WidgetsBindingObserver { ProviderSubscription? _localLibraryEnabledSub; Timer? _downloadHistoryWarmupTimer; Timer? _libraryCollectionsWarmupTimer; Timer? _localLibraryWarmupTimer; bool _localLibraryWarmupScheduled = false; + bool _autoScanTriggeredOnLaunch = false; + + static const _lastScannedAtKey = 'local_library_last_scanned_at'; @override void initState() { super.initState(); + WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addPostFrameCallback((_) { if (!mounted) return; _initializeAppServices(); @@ -110,6 +116,7 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { @override void dispose() { + WidgetsBinding.instance.removeObserver(this); _localLibraryEnabledSub?.close(); _downloadHistoryWarmupTimer?.cancel(); _libraryCollectionsWarmupTimer?.cancel(); @@ -117,6 +124,13 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { super.dispose(); } + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + if (state == AppLifecycleState.resumed) { + _maybeAutoScanLocalLibrary(); + } + } + void _initializeDeferredProviders() { _downloadHistoryWarmupTimer = _scheduleProviderWarmup( const Duration(milliseconds: 400), @@ -155,7 +169,64 @@ class _EagerInitializationState extends ConsumerState<_EagerInitialization> { _localLibraryWarmupScheduled = true; _localLibraryWarmupTimer = _scheduleProviderWarmup( const Duration(milliseconds: 1600), - () => ref.read(localLibraryProvider), + () { + ref.read(localLibraryProvider); + // Trigger auto-scan after initial warmup on first app launch. + if (!_autoScanTriggeredOnLaunch) { + _autoScanTriggeredOnLaunch = true; + // Give the provider a moment to load existing data before scanning. + Future.delayed(const Duration(milliseconds: 500), () { + if (mounted) _maybeAutoScanLocalLibrary(); + }); + } + }, + ); + } + + /// Checks whether an automatic incremental scan should be triggered based on + /// the user's auto-scan preference and the time since the last scan. + Future _maybeAutoScanLocalLibrary() async { + if (!mounted) return; + + final settings = ref.read(settingsProvider); + if (!settings.localLibraryEnabled) return; + if (settings.localLibraryPath.isEmpty) return; + if (settings.localLibraryAutoScan == 'off') return; + + // Don't start a scan if one is already running. + final libraryState = ref.read(localLibraryProvider); + if (libraryState.isScanning) return; + + // Determine cooldown based on auto-scan mode. + final now = DateTime.now(); + final prefs = await SharedPreferences.getInstance(); + final lastScannedMs = prefs.getInt(_lastScannedAtKey); + + if (lastScannedMs != null) { + final lastScanned = DateTime.fromMillisecondsSinceEpoch(lastScannedMs); + final elapsed = now.difference(lastScanned); + + switch (settings.localLibraryAutoScan) { + case 'on_open': + // Cooldown of 10 minutes to prevent rapid re-scans. + if (elapsed.inMinutes < 10) return; + break; + case 'daily': + if (elapsed.inHours < 24) return; + break; + case 'weekly': + if (elapsed.inDays < 7) return; + break; + default: + return; + } + } + + // All checks passed -- start an incremental scan. + final iosBookmark = settings.localLibraryBookmark; + ref.read(localLibraryProvider.notifier).startScan( + settings.localLibraryPath, + iosBookmark: iosBookmark.isNotEmpty ? iosBookmark : null, ); } diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 9bf2ee10..87c31e1b 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -59,6 +59,8 @@ class AppSettings { localLibraryBookmark; // Base64-encoded iOS security-scoped bookmark final bool localLibraryShowDuplicates; // Show indicator when searching for existing tracks + final String + localLibraryAutoScan; // Auto-scan mode: 'off', 'on_open', 'daily', 'weekly' final bool hasCompletedTutorial; // Track if user has completed the app tutorial @@ -123,6 +125,7 @@ class AppSettings { this.localLibraryPath = '', this.localLibraryBookmark = '', this.localLibraryShowDuplicates = true, + this.localLibraryAutoScan = 'off', this.hasCompletedTutorial = false, this.lyricsProviders = const [ 'lrclib', @@ -186,6 +189,7 @@ class AppSettings { String? localLibraryPath, String? localLibraryBookmark, bool? localLibraryShowDuplicates, + String? localLibraryAutoScan, bool? hasCompletedTutorial, List? lyricsProviders, bool? lyricsIncludeTranslationNetease, @@ -251,6 +255,8 @@ class AppSettings { localLibraryBookmark: localLibraryBookmark ?? this.localLibraryBookmark, localLibraryShowDuplicates: localLibraryShowDuplicates ?? this.localLibraryShowDuplicates, + localLibraryAutoScan: + localLibraryAutoScan ?? this.localLibraryAutoScan, hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial, lyricsProviders: lyricsProviders ?? this.lyricsProviders, lyricsIncludeTranslationNetease: diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index 99a5114d..2b6a35cf 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -57,6 +57,7 @@ AppSettings _$AppSettingsFromJson(Map json) => AppSettings( localLibraryBookmark: json['localLibraryBookmark'] as String? ?? '', localLibraryShowDuplicates: json['localLibraryShowDuplicates'] as bool? ?? true, + localLibraryAutoScan: json['localLibraryAutoScan'] as String? ?? 'off', hasCompletedTutorial: json['hasCompletedTutorial'] as bool? ?? false, lyricsProviders: (json['lyricsProviders'] as List?) @@ -129,6 +130,7 @@ Map _$AppSettingsToJson( 'localLibraryPath': instance.localLibraryPath, 'localLibraryBookmark': instance.localLibraryBookmark, 'localLibraryShowDuplicates': instance.localLibraryShowDuplicates, + 'localLibraryAutoScan': instance.localLibraryAutoScan, 'hasCompletedTutorial': instance.hasCompletedTutorial, 'lyricsProviders': instance.lyricsProviders, 'lyricsIncludeTranslationNetease': instance.lyricsIncludeTranslationNetease, diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index e98a70e0..33706c57 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -518,6 +518,11 @@ class SettingsNotifier extends Notifier { _saveSettings(); } + void setLocalLibraryAutoScan(String mode) { + state = state.copyWith(localLibraryAutoScan: mode); + _saveSettings(); + } + void setTutorialComplete() { state = state.copyWith(hasCompletedTutorial: true); _saveSettings(); diff --git a/lib/screens/settings/library_settings_page.dart b/lib/screens/settings/library_settings_page.dart index e71c1046..608eb4bf 100644 --- a/lib/screens/settings/library_settings_page.dart +++ b/lib/screens/settings/library_settings_page.dart @@ -241,6 +241,99 @@ class _LibrarySettingsPageState extends ConsumerState { } } + String _getAutoScanLabel(BuildContext context, String mode) { + switch (mode) { + case 'on_open': + return context.l10n.libraryAutoScanOnOpen; + case 'daily': + return context.l10n.libraryAutoScanDaily; + case 'weekly': + return context.l10n.libraryAutoScanWeekly; + default: + return context.l10n.libraryAutoScanOff; + } + } + + void _showAutoScanPicker(BuildContext context, String current) { + final colorScheme = Theme.of(context).colorScheme; + showModalBottomSheet( + context: context, + useRootNavigator: true, + backgroundColor: colorScheme.surfaceContainerHigh, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.vertical(top: Radius.circular(28)), + ), + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), + child: Text( + context.l10n.libraryAutoScan, + style: Theme.of(context) + .textTheme + .titleLarge + ?.copyWith(fontWeight: FontWeight.bold), + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.libraryAutoScanSubtitle, + style: Theme.of(context).textTheme.bodyMedium?.copyWith( + color: colorScheme.onSurfaceVariant, + ), + ), + ), + _AutoScanOption( + icon: Icons.block, + title: context.l10n.libraryAutoScanOff, + selected: current == 'off', + colorScheme: colorScheme, + onTap: () { + ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('off'); + Navigator.pop(context); + }, + ), + _AutoScanOption( + icon: Icons.open_in_new, + title: context.l10n.libraryAutoScanOnOpen, + selected: current == 'on_open', + colorScheme: colorScheme, + onTap: () { + ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('on_open'); + Navigator.pop(context); + }, + ), + _AutoScanOption( + icon: Icons.today, + title: context.l10n.libraryAutoScanDaily, + selected: current == 'daily', + colorScheme: colorScheme, + onTap: () { + ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('daily'); + Navigator.pop(context); + }, + ), + _AutoScanOption( + icon: Icons.date_range, + title: context.l10n.libraryAutoScanWeekly, + selected: current == 'weekly', + colorScheme: colorScheme, + onTap: () { + ref.read(settingsProvider.notifier).setLocalLibraryAutoScan('weekly'); + Navigator.pop(context); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ); + } + @override Widget build(BuildContext context) { final settings = ref.watch(settingsProvider); @@ -344,7 +437,18 @@ class _LibrarySettingsPageState extends ConsumerState { onChanged: (value) => ref .read(settingsProvider.notifier) .setLocalLibraryShowDuplicates(value), - showDivider: false, + ), + Opacity( + opacity: settings.localLibraryEnabled ? 1.0 : 0.5, + child: SettingsItem( + icon: Icons.autorenew_rounded, + title: context.l10n.libraryAutoScan, + subtitle: _getAutoScanLabel(context, settings.localLibraryAutoScan), + onTap: settings.localLibraryEnabled + ? () => _showAutoScanPicker(context, settings.localLibraryAutoScan) + : null, + showDivider: false, + ), ), ], ), @@ -825,3 +929,31 @@ class _ScanProgressTile extends StatelessWidget { ); } } + +class _AutoScanOption extends StatelessWidget { + final IconData icon; + final String title; + final bool selected; + final ColorScheme colorScheme; + final VoidCallback onTap; + + const _AutoScanOption({ + required this.icon, + required this.title, + required this.selected, + required this.colorScheme, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return ListTile( + leading: Icon(icon), + title: Text(title), + trailing: selected + ? Icon(Icons.check, color: colorScheme.primary) + : null, + onTap: onTap, + ); + } +}