mirror of
https://github.com/immich-app/immich.git
synced 2026-03-22 11:49:46 +03:00
fix(mobile): reflect asset deletions instantly (#26835)
Sometimes the current asset won't update when deleted, or it won't refresh until an event (like showing details) happens.
This commit is contained in:
@@ -14,13 +14,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
|||||||
Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
Future<void> performArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).archive(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).archive(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
final successMessage = 'archive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -57,13 +57,13 @@ class DeleteActionButton extends ConsumerWidget {
|
|||||||
if (confirm != true) return;
|
if (confirm != true) return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).trashRemoteAndDeleteLocal(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'delete_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
final successMessage = 'delete_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -35,13 +35,13 @@ class DeletePermanentActionButton extends ConsumerWidget {
|
|||||||
false;
|
false;
|
||||||
if (!confirm) return;
|
if (!confirm) return;
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).deleteRemoteAndLocal(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'delete_permanently_action_prompt'.t(
|
final successMessage = 'delete_permanently_action_prompt'.t(
|
||||||
context: context,
|
context: context,
|
||||||
args: {'count': result.count.toString()},
|
args: {'count': result.count.toString()},
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ import 'package:immich_mobile/widgets/common/immich_toast.dart';
|
|||||||
Future<void> performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
Future<void> performMoveToLockFolderAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).moveToLockFolder(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
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()},
|
||||||
|
|||||||
@@ -29,13 +29,13 @@ class RemoveFromAlbumActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).removeFromAlbum(source, albumId);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'remove_from_album_action_prompt'.t(
|
final successMessage = 'remove_from_album_action_prompt'.t(
|
||||||
context: context,
|
context: context,
|
||||||
args: {'count': result.count.toString()},
|
args: {'count': result.count.toString()},
|
||||||
|
|||||||
@@ -25,13 +25,13 @@ class TrashActionButton extends ConsumerWidget {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).trash(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).trash(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'trash_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
final successMessage = 'trash_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -16,13 +16,13 @@ import 'package:immich_mobile/domain/utils/event_stream.dart';
|
|||||||
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
Future<void> performUnArchiveAction(BuildContext context, WidgetRef ref, {required ActionSource source}) async {
|
||||||
if (!context.mounted) return;
|
if (!context.mounted) return;
|
||||||
|
|
||||||
final result = await ref.read(actionProvider.notifier).unArchive(source);
|
|
||||||
ref.read(multiSelectProvider.notifier).reset();
|
|
||||||
|
|
||||||
if (source == ActionSource.viewer) {
|
if (source == ActionSource.viewer) {
|
||||||
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
EventStream.shared.emit(const ViewerReloadAssetEvent());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final result = await ref.read(actionProvider.notifier).unArchive(source);
|
||||||
|
ref.read(multiSelectProvider.notifier).reset();
|
||||||
|
|
||||||
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
final successMessage = 'unarchive_action_prompt'.t(context: context, args: {'count': result.count.toString()});
|
||||||
|
|
||||||
if (context.mounted) {
|
if (context.mounted) {
|
||||||
|
|||||||
@@ -81,19 +81,17 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
late final _preloader = AssetPreloader(timelineService: ref.read(timelineServiceProvider), mounted: () => mounted);
|
late final _preloader = AssetPreloader(timelineService: ref.read(timelineServiceProvider), mounted: () => mounted);
|
||||||
|
|
||||||
late int _currentPage = widget.initialIndex;
|
late int _currentPage = widget.initialIndex;
|
||||||
|
late int _totalAssets = ref.read(timelineServiceProvider).totalAssets;
|
||||||
|
|
||||||
StreamSubscription? _reloadSubscription;
|
StreamSubscription? _reloadSubscription;
|
||||||
KeepAliveLink? _stackChildrenKeepAlive;
|
KeepAliveLink? _stackChildrenKeepAlive;
|
||||||
|
|
||||||
bool _assetReloadRequested = false;
|
|
||||||
|
|
||||||
void _onTapNavigate(int direction) {
|
void _onTapNavigate(int direction) {
|
||||||
final page = _pageController.page?.toInt();
|
final page = _pageController.page?.toInt();
|
||||||
if (page == null) return;
|
if (page == null) return;
|
||||||
final target = page + direction;
|
final target = page + direction;
|
||||||
final maxPage = ref.read(timelineServiceProvider).totalAssets - 1;
|
final maxPage = _totalAssets - 1;
|
||||||
if (target >= 0 && target <= maxPage) {
|
if (target >= 0 && target <= maxPage) {
|
||||||
_currentPage = target;
|
|
||||||
_pageController.jumpToPage(target);
|
_pageController.jumpToPage(target);
|
||||||
_onAssetChanged(target);
|
_onAssetChanged(target);
|
||||||
}
|
}
|
||||||
@@ -141,7 +139,6 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
|
|
||||||
final page = _pageController.page?.round();
|
final page = _pageController.page?.round();
|
||||||
if (page != null && page != _currentPage) {
|
if (page != null && page != _currentPage) {
|
||||||
_currentPage = page;
|
|
||||||
_onAssetChanged(page);
|
_onAssetChanged(page);
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -153,8 +150,9 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _onAssetChanged(int index) async {
|
void _onAssetChanged(int index) async {
|
||||||
final timelineService = ref.read(timelineServiceProvider);
|
_currentPage = index;
|
||||||
final asset = await timelineService.getAssetAsync(index);
|
|
||||||
|
final asset = await ref.read(timelineServiceProvider).getAssetAsync(index);
|
||||||
if (asset == null) return;
|
if (asset == null) return;
|
||||||
|
|
||||||
AssetViewer._setAsset(ref, asset);
|
AssetViewer._setAsset(ref, asset);
|
||||||
@@ -193,11 +191,20 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
case TimelineReloadEvent():
|
case TimelineReloadEvent():
|
||||||
_onTimelineReloadEvent();
|
_onTimelineReloadEvent();
|
||||||
case ViewerReloadAssetEvent():
|
case ViewerReloadAssetEvent():
|
||||||
_assetReloadRequested = true;
|
_onViewerReloadEvent();
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _onViewerReloadEvent() {
|
||||||
|
if (_totalAssets <= 1) return;
|
||||||
|
|
||||||
|
final index = _pageController.page?.round() ?? 0;
|
||||||
|
final target = index >= _totalAssets - 1 ? index - 1 : index + 1;
|
||||||
|
_pageController.animateToPage(target, duration: Durations.medium1, curve: Curves.easeInOut);
|
||||||
|
_onAssetChanged(target);
|
||||||
|
}
|
||||||
|
|
||||||
void _onTimelineReloadEvent() {
|
void _onTimelineReloadEvent() {
|
||||||
final timelineService = ref.read(timelineServiceProvider);
|
final timelineService = ref.read(timelineServiceProvider);
|
||||||
final totalAssets = timelineService.totalAssets;
|
final totalAssets = timelineService.totalAssets;
|
||||||
@@ -207,43 +214,24 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var index = _pageController.page?.round() ?? 0;
|
|
||||||
final currentAsset = ref.read(assetViewerProvider).currentAsset;
|
final currentAsset = ref.read(assetViewerProvider).currentAsset;
|
||||||
if (currentAsset != null) {
|
final assetIndex = currentAsset != null ? timelineService.getIndex(currentAsset.heroTag) : null;
|
||||||
final newIndex = timelineService.getIndex(currentAsset.heroTag);
|
final index = (assetIndex ?? _currentPage).clamp(0, totalAssets - 1);
|
||||||
if (newIndex != null && newIndex != index) {
|
|
||||||
index = newIndex;
|
|
||||||
_currentPage = index;
|
|
||||||
_pageController.jumpToPage(index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (index >= totalAssets) {
|
if (index != _currentPage) {
|
||||||
index = totalAssets - 1;
|
|
||||||
_currentPage = index;
|
|
||||||
_pageController.jumpToPage(index);
|
_pageController.jumpToPage(index);
|
||||||
|
_onAssetChanged(index);
|
||||||
|
} else if (currentAsset != null && assetIndex == null) {
|
||||||
|
_onAssetChanged(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_assetReloadRequested) {
|
if (_totalAssets != totalAssets) {
|
||||||
_assetReloadRequested = false;
|
setState(() {
|
||||||
_onAssetReloadEvent(index);
|
_totalAssets = totalAssets;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAssetReloadEvent(int index) async {
|
|
||||||
final timelineService = ref.read(timelineServiceProvider);
|
|
||||||
|
|
||||||
final newAsset = await timelineService.getAssetAsync(index);
|
|
||||||
if (newAsset == null) return;
|
|
||||||
|
|
||||||
final currentAsset = ref.read(assetViewerProvider).currentAsset;
|
|
||||||
|
|
||||||
// Do not reload if the asset has not changed
|
|
||||||
if (newAsset.heroTag == currentAsset?.heroTag) return;
|
|
||||||
|
|
||||||
_onAssetChanged(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _setSystemUIMode(bool controls, bool details) {
|
void _setSystemUIMode(bool controls, bool details) {
|
||||||
final mode = !controls || (CurrentPlatform.isIOS && details)
|
final mode = !controls || (CurrentPlatform.isIOS && details)
|
||||||
? SystemUiMode.immersiveSticky
|
? SystemUiMode.immersiveSticky
|
||||||
@@ -301,7 +289,7 @@ class _AssetViewerState extends ConsumerState<AssetViewer> {
|
|||||||
: CurrentPlatform.isIOS
|
: CurrentPlatform.isIOS
|
||||||
? const FastScrollPhysics()
|
? const FastScrollPhysics()
|
||||||
: const FastClampingScrollPhysics(),
|
: const FastClampingScrollPhysics(),
|
||||||
itemCount: ref.read(timelineServiceProvider).totalAssets,
|
itemCount: _totalAssets,
|
||||||
itemBuilder: (context, index) =>
|
itemBuilder: (context, index) =>
|
||||||
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
|
AssetPage(index: index, heroOffset: _heroOffset, onTapNavigate: _onTapNavigate),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user