perf(ui): scope scan rebuilds, aspect-bound MediaQuery, lazy duplicate list

This commit is contained in:
zarzet
2026-07-26 22:25:07 +07:00
parent cbee637501
commit 2ce793d68f
3 changed files with 97 additions and 71 deletions
+1 -1
View File
@@ -154,7 +154,7 @@ String? _formatDownloadEta(DownloadItem item, {double? visualProgress}) {
bool _shouldAnimateDownloadProgress(BuildContext context, DownloadItem item) {
final progress = item.progress.clamp(0.0, 1.0);
final animationsDisabled =
MediaQuery.maybeOf(context)?.disableAnimations ?? false;
MediaQuery.maybeDisableAnimationsOf(context) ?? false;
return item.status == DownloadStatus.downloading &&
progress > 0 &&
progress < 1 &&
+90 -69
View File
@@ -340,7 +340,6 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
@override
Widget build(BuildContext context) {
final settings = ref.watch(settingsProvider);
final libraryState = ref.watch(localLibraryProvider);
final colorScheme = Theme.of(context).colorScheme;
return Scaffold(
@@ -348,17 +347,25 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
slivers: [
SettingsSliverAppBar(title: context.l10n.libraryTitle),
// Scan snapshots land ~2.5x/s; keep the per-snapshot rebuild scoped
// to the widgets that actually display scan state instead of the
// whole settings page.
SliverToBoxAdapter(
child: _LibraryHeroCard(
itemCount: libraryState.totalCount,
excludedDownloadedCount: libraryState.excludedDownloadedCount,
isScanning: libraryState.isScanning,
scanIsFinalizing: libraryState.scanIsFinalizing,
scanProgress: libraryState.scanProgress,
scanCurrentFile: libraryState.scanCurrentFile,
scanTotalFiles: libraryState.scanTotalFiles,
scannedFiles: libraryState.scannedFiles,
lastScannedAt: libraryState.lastScannedAt,
child: Consumer(
builder: (context, ref, _) {
final libraryState = ref.watch(localLibraryProvider);
return _LibraryHeroCard(
itemCount: libraryState.totalCount,
excludedDownloadedCount: libraryState.excludedDownloadedCount,
isScanning: libraryState.isScanning,
scanIsFinalizing: libraryState.scanIsFinalizing,
scanProgress: libraryState.scanProgress,
scanCurrentFile: libraryState.scanCurrentFile,
scanTotalFiles: libraryState.scanTotalFiles,
scannedFiles: libraryState.scannedFiles,
lastScannedAt: libraryState.lastScannedAt,
);
},
),
),
@@ -438,7 +445,9 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
SliverToBoxAdapter(
child: SettingsSectionHeader(title: context.l10n.libraryActions),
),
if (libraryState.scanWasCancelled)
if (ref.watch(
localLibraryProvider.select((s) => s.scanWasCancelled),
))
SliverToBoxAdapter(
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 4),
@@ -491,65 +500,77 @@ class _LibrarySettingsPageState extends ConsumerState<LibrarySettingsPage> {
),
),
SliverToBoxAdapter(
child: SettingsGroup(
children: [
if (libraryState.isScanning)
_ScanProgressTile(
isFinalizing: libraryState.scanIsFinalizing,
progress: libraryState.scanProgress,
currentFile: libraryState.scanCurrentFile,
scannedFiles: libraryState.scannedFiles,
totalFiles: libraryState.scanTotalFiles,
onCancel: _cancelScan,
)
else ...[
Opacity(
opacity: settings.localLibraryPath.isNotEmpty ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.refresh,
title: context.l10n.libraryScan,
subtitle: settings.localLibraryPath.isEmpty
? context.l10n.libraryScanSelectFolderFirst
: context.l10n.libraryScanSubtitle,
onTap: settings.localLibraryPath.isNotEmpty
? _startScan
: null,
child: Consumer(
builder: (context, ref, _) {
final libraryState = ref.watch(localLibraryProvider);
return SettingsGroup(
children: [
if (libraryState.isScanning)
_ScanProgressTile(
isFinalizing: libraryState.scanIsFinalizing,
progress: libraryState.scanProgress,
currentFile: libraryState.scanCurrentFile,
scannedFiles: libraryState.scannedFiles,
totalFiles: libraryState.scanTotalFiles,
onCancel: _cancelScan,
)
else ...[
Opacity(
opacity: settings.localLibraryPath.isNotEmpty
? 1.0
: 0.5,
child: SettingsItem(
icon: Icons.refresh,
title: context.l10n.libraryScan,
subtitle: settings.localLibraryPath.isEmpty
? context.l10n.libraryScanSelectFolderFirst
: context.l10n.libraryScanSubtitle,
onTap: settings.localLibraryPath.isNotEmpty
? _startScan
: null,
),
),
Opacity(
opacity: settings.localLibraryPath.isNotEmpty
? 1.0
: 0.5,
child: SettingsItem(
icon: Icons.sync,
title: context.l10n.libraryForceFullScan,
subtitle: context.l10n.libraryForceFullScanSubtitle,
onTap: settings.localLibraryPath.isNotEmpty
? () => _startScan(forceFullScan: true)
: null,
),
),
],
Opacity(
opacity: libraryState.totalCount > 0 ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.cleaning_services_outlined,
title: context.l10n.libraryCleanupMissingFiles,
subtitle:
context.l10n.libraryCleanupMissingFilesSubtitle,
onTap: libraryState.totalCount > 0
? _cleanupMissingFiles
: null,
),
),
),
Opacity(
opacity: settings.localLibraryPath.isNotEmpty ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.sync,
title: context.l10n.libraryForceFullScan,
subtitle: context.l10n.libraryForceFullScanSubtitle,
onTap: settings.localLibraryPath.isNotEmpty
? () => _startScan(forceFullScan: true)
: null,
Opacity(
opacity: libraryState.totalCount > 0 ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.delete_outline,
title: context.l10n.libraryClear,
subtitle: context.l10n.libraryClearSubtitle,
onTap: libraryState.totalCount > 0
? _clearLibrary
: null,
showDivider: false,
),
),
),
],
Opacity(
opacity: libraryState.totalCount > 0 ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.cleaning_services_outlined,
title: context.l10n.libraryCleanupMissingFiles,
subtitle: context.l10n.libraryCleanupMissingFilesSubtitle,
onTap: libraryState.totalCount > 0
? _cleanupMissingFiles
: null,
),
),
Opacity(
opacity: libraryState.totalCount > 0 ? 1.0 : 0.5,
child: SettingsItem(
icon: Icons.delete_outline,
title: context.l10n.libraryClear,
subtitle: context.l10n.libraryClearSubtitle,
onTap: libraryState.totalCount > 0 ? _clearLibrary : null,
showDivider: false,
),
),
],
],
);
},
),
),
],
+6 -1
View File
@@ -205,8 +205,13 @@ class _DuplicateReviewSheetState extends ConsumerState<DuplicateReviewSheet> {
),
);
}
// shrinkWrap keeps a small sheet compact but builds every
// group eagerly; past a screenful, switch to a lazy
// full-height list so a large duplicate set can't jank the
// opening frame.
final compact = groups.length <= 12;
return ListView.builder(
shrinkWrap: true,
shrinkWrap: compact,
itemCount: groups.length,
itemBuilder: (context, index) =>
_buildGroup(context, colorScheme, groups[index]),