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 <alex.tran1502@gmail.com>
This commit is contained in:
shenlong 2025-07-22 22:28:34 +05:30 committed by GitHub
parent aa344a3989
commit ab61bcfcc8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 67 additions and 17 deletions

View File

@ -10,7 +10,7 @@ const int kSyncEventBatchSize = 5000;
const int kFetchLocalAssetsBatchSize = 40000; const int kFetchLocalAssetsBatchSize = 40000;
// Hash batch limits // Hash batch limits
const int kBatchHashFileLimit = 128; const int kBatchHashFileLimit = 256;
const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB const int kBatchHashSizeLimit = 1024 * 1024 * 1024; // 1GB
// Secure storage keys // Secure storage keys

View File

@ -62,7 +62,8 @@ class RemoteAssetRepository extends DriftDatabaseRepository {
_db.remoteAssetEntity.id, _db.remoteAssetEntity.id,
_db.localAssetEntity.id, _db.localAssetEntity.id,
_db.stackEntity.primaryAssetId, _db.stackEntity.primaryAssetId,
]); ])
..limit(1);
return query.map((row) { return query.map((row) {
final asset = row.readTable(_db.remoteAssetEntity).toDto(); final asset = row.readTable(_db.remoteAssetEntity).toDto();

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.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/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.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); final result = await ref.read(actionProvider.notifier).archive(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'archive_action_prompt'.t( final successMessage = 'archive_action_prompt'.t(
context: context, context: context,
args: {'count': result.count.toString()}, args: {'count': result.count.toString()},

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.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/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.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); final result = await ref.read(actionProvider.notifier).deleteLocal(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'delete_local_action_prompt'.t( final successMessage = 'delete_local_action_prompt'.t(
context: context, context: context,
args: {'count': result.count.toString()}, args: {'count': result.count.toString()},

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.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/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.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); await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'delete_action_prompt'.t( final successMessage = 'delete_action_prompt'.t(
context: context, context: context,
args: {'count': result.count.toString()}, args: {'count': result.count.toString()},

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.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/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.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); await ref.read(actionProvider.notifier).moveToLockFolder(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'move_to_lock_folder_action_prompt'.t( final successMessage = 'move_to_lock_folder_action_prompt'.t(
context: context, context: context,
args: {'count': result.count.toString()}, args: {'count': result.count.toString()},

View File

@ -2,8 +2,10 @@ import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart'; import 'package:fluttertoast/fluttertoast.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:immich_mobile/constants/enums.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/extensions/translate_extensions.dart';
import 'package:immich_mobile/presentation/widgets/action_buttons/base_action_button.widget.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/infrastructure/action.provider.dart';
import 'package:immich_mobile/providers/timeline/multiselect.provider.dart'; import 'package:immich_mobile/providers/timeline/multiselect.provider.dart';
import 'package:immich_mobile/widgets/common/immich_toast.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); final result = await ref.read(actionProvider.notifier).trash(source);
ref.read(multiSelectProvider.notifier).reset(); ref.read(multiSelectProvider.notifier).reset();
if (source == ActionSource.viewer) {
EventStream.shared.emit(const ViewerReloadAssetEvent());
}
final successMessage = 'trash_action_prompt'.t( final successMessage = 'trash_action_prompt'.t(
context: context, context: context,
args: {'count': result.count.toString()}, args: {'count': result.count.toString()},

View File

@ -85,6 +85,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
bool blockGestures = false; bool blockGestures = false;
bool dragInProgress = false; bool dragInProgress = false;
bool shouldPopOnDrag = false; bool shouldPopOnDrag = false;
bool assetReloadRequested = false;
double? initialScale; double? initialScale;
double previousExtent = _kBottomSheetMinimumExtent; double previousExtent = _kBottomSheetMinimumExtent;
Offset dragDownPosition = Offset.zero; Offset dragDownPosition = Offset.zero;
@ -404,7 +405,12 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
void _onEvent(Event event) { void _onEvent(Event event) {
if (event is TimelineReloadEvent) { if (event is TimelineReloadEvent) {
_onTimelineReload(event); _onTimelineReloadEvent();
return;
}
if (event is ViewerReloadAssetEvent) {
assetReloadRequested = true;
return; return;
} }
@ -417,14 +423,22 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
} }
} }
void _onTimelineReload(_) { void _onTimelineReloadEvent() {
setState(() { totalAssets = ref.read(timelineServiceProvider).totalAssets;
totalAssets = ref.read(timelineServiceProvider).totalAssets; if (totalAssets == 0) {
if (totalAssets == 0) { context.maybePop();
context.maybePop(); return;
return; }
}
if (assetReloadRequested) {
assetReloadRequested = false;
_onAssetReloadEvent();
return;
}
}
void _onAssetReloadEvent() {
setState(() {
final index = pageController.page?.round() ?? 0; final index = pageController.page?.round() ?? 0;
final newAsset = ref.read(timelineServiceProvider).getAsset(index); final newAsset = ref.read(timelineServiceProvider).getAsset(index);
final currentAsset = ref.read(currentAssetNotifier); final currentAsset = ref.read(currentAssetNotifier);

View File

@ -7,6 +7,10 @@ class ViewerOpenBottomSheetEvent extends Event {
const ViewerOpenBottomSheetEvent(); const ViewerOpenBottomSheetEvent();
} }
class ViewerReloadAssetEvent extends Event {
const ViewerReloadAssetEvent();
}
class AssetViewerState { class AssetViewerState {
final int backgroundOpacity; final int backgroundOpacity;
final bool showingBottomSheet; final bool showingBottomSheet;

View File

@ -113,13 +113,14 @@ class MapThumbnail extends HookConsumerWidget {
), ),
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: position, valueListenable: position,
builder: (_, value, __) => value != null builder: (_, value, __) =>
? PositionedAssetMarkerIcon( value != null && assetMarkerRemoteId != null
size: height / 2, ? PositionedAssetMarkerIcon(
point: value, size: height / 2,
assetRemoteId: assetMarkerRemoteId!, point: value,
) assetRemoteId: assetMarkerRemoteId!,
: const SizedBox.shrink(), )
: const SizedBox.shrink(),
), ),
], ],
), ),