From ab61bcfcc8c3c94aa1941f0b24352558eb05e2b6 Mon Sep 17 00:00:00 2001 From: shenlong <139912620+shenlong-tanwen@users.noreply.github.com> Date: Tue, 22 Jul 2025 22:28:34 +0530 Subject: [PATCH] fix: reduce asset_viewer reloads (#20083) * fix: reduce asset_viewer reloads * limit the result of watch asset to one * fix null reference in map thumbnail * bump hash file limit to 256 --------- Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com> Co-authored-by: Alex --- mobile/lib/constants/constants.dart | 2 +- .../repositories/remote_asset.repository.dart | 3 +- .../archive_action_button.widget.dart | 6 ++++ .../delete_local_action_button.widget.dart | 6 ++++ ...delete_permanent_action_button.widget.dart | 6 ++++ ...e_to_lock_folder_action_button.widget.dart | 6 ++++ .../trash_action_button.widget.dart | 6 ++++ .../asset_viewer/asset_viewer.page.dart | 30 ++++++++++++++----- .../asset_viewer/asset_viewer.state.dart | 4 +++ mobile/lib/widgets/map/map_thumbnail.dart | 15 +++++----- 10 files changed, 67 insertions(+), 17 deletions(-) diff --git a/mobile/lib/constants/constants.dart b/mobile/lib/constants/constants.dart index 206fbbb28f..b54a1e9ca2 100644 --- a/mobile/lib/constants/constants.dart +++ b/mobile/lib/constants/constants.dart @@ -10,7 +10,7 @@ const int kSyncEventBatchSize = 5000; const int kFetchLocalAssetsBatchSize = 40000; // Hash batch limits -const int kBatchHashFileLimit = 128; +const int kBatchHashFileLimit = 256; const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB // Secure storage keys diff --git a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart index 2c5006953f..64c602a2c7 100644 --- a/mobile/lib/infrastructure/repositories/remote_asset.repository.dart +++ b/mobile/lib/infrastructure/repositories/remote_asset.repository.dart @@ -62,7 +62,8 @@ class RemoteAssetRepository extends DriftDatabaseRepository { _db.remoteAssetEntity.id, _db.localAssetEntity.id, _db.stackEntity.primaryAssetId, - ]); + ]) + ..limit(1); return query.map((row) { final asset = row.readTable(_db.remoteAssetEntity).toDto(); diff --git a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart index 86537816e3..061e15e58a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/archive_action_button.widget.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -21,6 +23,10 @@ class ArchiveActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).archive(source); ref.read(multiSelectProvider.notifier).reset(); + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + final successMessage = 'archive_action_prompt'.t( context: context, args: {'count': result.count.toString()}, diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart index 90534ca68c..acd3738f7a 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_local_action_button.widget.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -21,6 +23,10 @@ class DeleteLocalActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).deleteLocal(source); ref.read(multiSelectProvider.notifier).reset(); + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + final successMessage = 'delete_local_action_prompt'.t( context: context, args: {'count': result.count.toString()}, diff --git a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart index 9f88d640dd..7840e8a41d 100644 --- a/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/delete_permanent_action_button.widget.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -22,6 +24,10 @@ class DeletePermanentActionButton extends ConsumerWidget { await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source); ref.read(multiSelectProvider.notifier).reset(); + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + final successMessage = 'delete_action_prompt'.t( context: context, args: {'count': result.count.toString()}, diff --git a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart index 7546f07961..6b3e32a2dc 100644 --- a/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/move_to_lock_folder_action_button.widget.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -22,6 +24,10 @@ class MoveToLockFolderActionButton extends ConsumerWidget { await ref.read(actionProvider.notifier).moveToLockFolder(source); ref.read(multiSelectProvider.notifier).reset(); + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + final successMessage = 'move_to_lock_folder_action_prompt'.t( context: context, args: {'count': result.count.toString()}, diff --git a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart index 449b688550..2863c71d12 100644 --- a/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart +++ b/mobile/lib/presentation/widgets/action_buttons/trash_action_button.widget.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/constants/enums.dart'; +import 'package:immich_mobile/domain/utils/event_stream.dart'; import 'package:immich_mobile/extensions/translate_extensions.dart'; import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.dart'; +import 'package:immich_mobile/presentation/widgets/asset_viewer/asset_viewer.state.dart'; import 'package:immich_mobile/providers/infrastructure/action.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; @@ -21,6 +23,10 @@ class TrashActionButton extends ConsumerWidget { final result = await ref.read(actionProvider.notifier).trash(source); ref.read(multiSelectProvider.notifier).reset(); + if (source == ActionSource.viewer) { + EventStream.shared.emit(const ViewerReloadAssetEvent()); + } + final successMessage = 'trash_action_prompt'.t( context: context, args: {'count': result.count.toString()}, diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart index 9356c2f43e..34339c8ba3 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.page.dart @@ -85,6 +85,7 @@ class _AssetViewerState extends ConsumerState { bool blockGestures = false; bool dragInProgress = false; bool shouldPopOnDrag = false; + bool assetReloadRequested = false; double? initialScale; double previousExtent = _kBottomSheetMinimumExtent; Offset dragDownPosition = Offset.zero; @@ -404,7 +405,12 @@ class _AssetViewerState extends ConsumerState { void _onEvent(Event event) { if (event is TimelineReloadEvent) { - _onTimelineReload(event); + _onTimelineReloadEvent(); + return; + } + + if (event is ViewerReloadAssetEvent) { + assetReloadRequested = true; return; } @@ -417,14 +423,22 @@ class _AssetViewerState extends ConsumerState { } } - void _onTimelineReload(_) { - setState(() { - totalAssets = ref.read(timelineServiceProvider).totalAssets; - if (totalAssets == 0) { - context.maybePop(); - return; - } + void _onTimelineReloadEvent() { + totalAssets = ref.read(timelineServiceProvider).totalAssets; + if (totalAssets == 0) { + context.maybePop(); + return; + } + if (assetReloadRequested) { + assetReloadRequested = false; + _onAssetReloadEvent(); + return; + } + } + + void _onAssetReloadEvent() { + setState(() { final index = pageController.page?.round() ?? 0; final newAsset = ref.read(timelineServiceProvider).getAsset(index); final currentAsset = ref.read(currentAssetNotifier); diff --git a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart index 825b637e8d..e57a6ff7ca 100644 --- a/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart +++ b/mobile/lib/presentation/widgets/asset_viewer/asset_viewer.state.dart @@ -7,6 +7,10 @@ class ViewerOpenBottomSheetEvent extends Event { const ViewerOpenBottomSheetEvent(); } +class ViewerReloadAssetEvent extends Event { + const ViewerReloadAssetEvent(); +} + class AssetViewerState { final int backgroundOpacity; final bool showingBottomSheet; diff --git a/mobile/lib/widgets/map/map_thumbnail.dart b/mobile/lib/widgets/map/map_thumbnail.dart index 1e4b061be6..5be15f2d5f 100644 --- a/mobile/lib/widgets/map/map_thumbnail.dart +++ b/mobile/lib/widgets/map/map_thumbnail.dart @@ -113,13 +113,14 @@ class MapThumbnail extends HookConsumerWidget { ), ValueListenableBuilder( valueListenable: position, - builder: (_, value, __) => value != null - ? PositionedAssetMarkerIcon( - size: height / 2, - point: value, - assetRemoteId: assetMarkerRemoteId!, - ) - : const SizedBox.shrink(), + builder: (_, value, __) => + value != null && assetMarkerRemoteId != null + ? PositionedAssetMarkerIcon( + size: height / 2, + point: value, + assetRemoteId: assetMarkerRemoteId!, + ) + : const SizedBox.shrink(), ), ], ),