From b1180a70a7bc21fa6d0e73f6b3cd917a08abda8d Mon Sep 17 00:00:00 2001 From: zarzet Date: Sat, 22 Aug 2026 13:43:06 +0700 Subject: [PATCH] fix(library): restore live folder scan progress --- lib/l10n/app_localizations.dart | 6 + lib/l10n/app_localizations_de.dart | 5 + lib/l10n/app_localizations_en.dart | 5 + lib/l10n/app_localizations_es.dart | 5 + lib/l10n/app_localizations_fr.dart | 5 + lib/l10n/app_localizations_id.dart | 5 + lib/l10n/app_localizations_ja.dart | 5 + lib/l10n/app_localizations_ko.dart | 5 + lib/l10n/app_localizations_pt.dart | 5 + lib/l10n/app_localizations_ru.dart | 5 + lib/l10n/app_localizations_tr.dart | 5 + lib/l10n/app_localizations_uk.dart | 5 + lib/l10n/arb/app_en.arb | 9 ++ lib/l10n/arb/app_id.arb | 1 + .../settings/library_settings_page.dart | 126 ++++++++++++++---- 15 files changed, 174 insertions(+), 23 deletions(-) diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index cadae656..ff1708a3 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -3430,6 +3430,12 @@ abstract class AppLocalizations { /// **'Disabled'** String get librarySourceDisabled; + /// Live progress for one library folder scan + /// + /// In en, this message translates to: + /// **'{scanned} of {total} files scanned ({progress}%)'** + String librarySourceScanCount(int scanned, int total, String progress); + /// Label for a removable or external library source /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index d3e18173..d10979dd 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -1918,6 +1918,11 @@ class AppLocalizationsDe extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 386e864e..2fad9d9a 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -1893,6 +1893,11 @@ class AppLocalizationsEn extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 775a2b80..ef758468 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -1893,6 +1893,11 @@ class AppLocalizationsEs extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index a4dfa7cf..c059368f 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -1944,6 +1944,11 @@ class AppLocalizationsFr extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index b251b866..7613fe87 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -1902,6 +1902,11 @@ class AppLocalizationsId extends AppLocalizations { @override String get librarySourceDisabled => 'Dinonaktifkan'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned dari $total file dipindai ($progress%)'; + } + @override String get libraryExternalStorage => 'Storage eksternal'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 1dbf6c84..72855590 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -1882,6 +1882,11 @@ class AppLocalizationsJa extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index cd304012..580257e9 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -1853,6 +1853,11 @@ class AppLocalizationsKo extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 62efa9b7..f0a928f5 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -1893,6 +1893,11 @@ class AppLocalizationsPt extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4be46537..0d5187cb 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -1913,6 +1913,11 @@ class AppLocalizationsRu extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index cf8297d5..6cb39c3d 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -1916,6 +1916,11 @@ class AppLocalizationsTr extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index 42d7e997..e190a93c 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -1922,6 +1922,11 @@ class AppLocalizationsUk extends AppLocalizations { @override String get librarySourceDisabled => 'Disabled'; + @override + String librarySourceScanCount(int scanned, int total, String progress) { + return '$scanned of $total files scanned ($progress%)'; + } + @override String get libraryExternalStorage => 'External storage'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 953e750b..303d3a9a 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -2517,6 +2517,15 @@ "@librarySourceDisabled": { "description": "Library source has been disabled by the user" }, + "librarySourceScanCount": "{scanned} of {total} files scanned ({progress}%)", + "@librarySourceScanCount": { + "description": "Live progress for one library folder scan", + "placeholders": { + "scanned": {"type": "int"}, + "total": {"type": "int"}, + "progress": {"type": "String"} + } + }, "libraryExternalStorage": "External storage", "@libraryExternalStorage": { "description": "Label for a removable or external library source" diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 63a42b20..ad93e7b9 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -3314,6 +3314,7 @@ "librarySourceOnline": "Online", "librarySourceOffline": "Offline. Sambungkan kembali storage untuk memulihkan lagu", "librarySourceDisabled": "Dinonaktifkan", + "librarySourceScanCount": "{scanned} dari {total} file dipindai ({progress}%)", "libraryExternalStorage": "Storage eksternal", "libraryRemoveFolder": "Hapus folder library", "libraryRemoveFolderMessage": "Hapus folder ini dan indeks lagunya dari SpotiFLAC Mobile? File audio di storage tidak akan dihapus.", diff --git a/lib/screens/settings/library_settings_page.dart b/lib/screens/settings/library_settings_page.dart index 4c22fbea..0610ab94 100644 --- a/lib/screens/settings/library_settings_page.dart +++ b/lib/screens/settings/library_settings_page.dart @@ -511,9 +511,6 @@ class _LibrarySettingsPageState extends ConsumerState { final librarySources = ref.watch( localLibraryProvider.select((state) => state.sources), ); - final scanningSourceId = ref.watch( - localLibraryProvider.select((state) => state.scanningSourceId), - ); final colorScheme = Theme.of(context).colorScheme; return Scaffold( @@ -595,20 +592,40 @@ class _LibrarySettingsPageState extends ConsumerState { .setLocalLibraryEnabled(value), ), for (final source in librarySources) - _LibrarySourceSettingsItem( - source: source, - isScanning: scanningSourceId == source.id, - enabled: settings.localLibraryEnabled, - onEnabledChanged: (value) => ref - .read(localLibraryProvider.notifier) - .setSourceEnabled(source.id, value), - onScan: () => ref - .read(localLibraryProvider.notifier) - .startSourceScan(source.id), - onFullScan: () => ref - .read(localLibraryProvider.notifier) - .startSourceScan(source.id, forceFullScan: true), - onRemove: () => _removeSource(source), + Consumer( + builder: (context, ref, _) { + final scan = ref.watch( + localLibraryProvider.select((state) { + final active = state.scanningSourceId == source.id; + return ( + active: active, + finalizing: active && state.scanIsFinalizing, + scanned: active ? state.scannedFiles : 0, + total: active ? state.scanTotalFiles : 0, + progress: active ? state.scanProgress : 0.0, + ); + }), + ); + return _LibrarySourceSettingsItem( + source: source, + isScanning: scan.active, + isFinalizing: scan.finalizing, + scannedFiles: scan.scanned, + totalFiles: scan.total, + progress: scan.progress, + enabled: settings.localLibraryEnabled, + onEnabledChanged: (value) => ref + .read(localLibraryProvider.notifier) + .setSourceEnabled(source.id, value), + onScan: () => ref + .read(localLibraryProvider.notifier) + .startSourceScan(source.id), + onFullScan: () => ref + .read(localLibraryProvider.notifier) + .startSourceScan(source.id, forceFullScan: true), + onRemove: () => _removeSource(source), + ); + }, ), Opacity( opacity: settings.localLibraryEnabled ? 1.0 : 0.5, @@ -901,6 +918,10 @@ class _LibrarySettingsPageState extends ConsumerState { class _LibrarySourceSettingsItem extends StatelessWidget { final LocalLibrarySource source; final bool isScanning; + final bool isFinalizing; + final int scannedFiles; + final int totalFiles; + final double progress; final bool enabled; final ValueChanged onEnabledChanged; final VoidCallback onScan; @@ -910,6 +931,10 @@ class _LibrarySourceSettingsItem extends StatelessWidget { const _LibrarySourceSettingsItem({ required this.source, required this.isScanning, + required this.isFinalizing, + required this.scannedFiles, + required this.totalFiles, + required this.progress, required this.enabled, required this.onEnabledChanged, required this.onScan, @@ -994,7 +1019,15 @@ class _LibrarySourceSettingsItem extends StatelessWidget { : !source.available ? context.l10n.librarySourceOffline : isScanning - ? context.l10n.libraryScanning + ? isFinalizing + ? context.l10n.libraryScanFinalizing + : totalFiles > 0 + ? context.l10n.librarySourceScanCount( + scannedFiles, + totalFiles, + progress.toStringAsFixed(0), + ) + : context.l10n.libraryScanning : source.lastScanError?.trim().isNotEmpty == true ? context.l10n.notifLibraryScanFailed : context.l10n.librarySourceOnline; @@ -1022,11 +1055,12 @@ class _LibrarySourceSettingsItem extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ if (isScanning) - const Padding( - padding: EdgeInsets.only(right: 8), - child: SizedBox.square( - dimension: 18, - child: CircularProgressIndicator(strokeWidth: 2), + Padding( + padding: const EdgeInsets.only(right: 8), + child: _AnimatedScanCount( + count: scannedFiles, + progress: progress, + determinate: totalFiles > 0 && !isFinalizing, ), ), Switch.adaptive( @@ -1040,6 +1074,52 @@ class _LibrarySourceSettingsItem extends StatelessWidget { } } +class _AnimatedScanCount extends StatelessWidget { + final int count; + final double progress; + final bool determinate; + + const _AnimatedScanCount({ + required this.count, + required this.progress, + required this.determinate, + }); + + @override + Widget build(BuildContext context) { + final colorScheme = Theme.of(context).colorScheme; + return SizedBox( + width: 42, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox.square( + dimension: 18, + child: CircularProgressIndicator( + value: determinate ? (progress / 100).clamp(0.0, 1.0) : null, + strokeWidth: 2, + ), + ), + const SizedBox(height: 2), + TweenAnimationBuilder( + tween: Tween(begin: 0, end: count.toDouble()), + duration: const Duration(milliseconds: 250), + curve: Curves.easeOut, + builder: (context, value, _) => Text( + value.round().toString(), + maxLines: 1, + style: Theme.of(context).textTheme.labelSmall?.copyWith( + color: colorScheme.onSurfaceVariant, + fontFeatures: const [FontFeature.tabularFigures()], + ), + ), + ), + ], + ), + ); + } +} + class _LibraryHeroCard extends StatelessWidget { final int itemCount; final int excludedDownloadedCount;