diff --git a/mobile/assets/i18n/en-US.json b/mobile/assets/i18n/en-US.json index b584d99a6a920..11a41b50f58a9 100644 --- a/mobile/assets/i18n/en-US.json +++ b/mobile/assets/i18n/en-US.json @@ -41,6 +41,8 @@ "album_viewer_appbar_share_remove": "Remove from album", "album_viewer_appbar_share_to": "Share To", "album_viewer_page_share_add_users": "Add users", + "album_viewer_add_photos_exit_dialog_title": "Confirm Exit", + "album_viewer_add_photos_exit_dialog_content": "Your selected photos will not be saved. Are you sure you want to exit this page?", "all": "All", "all_people_page_title": "People", "all_videos_page_title": "Videos", diff --git a/mobile/lib/pages/album/album_asset_selection.page.dart b/mobile/lib/pages/album/album_asset_selection.page.dart index 18ceb3e144563..7dd99ad238562 100644 --- a/mobile/lib/pages/album/album_asset_selection.page.dart +++ b/mobile/lib/pages/album/album_asset_selection.page.dart @@ -1,15 +1,16 @@ import 'package:auto_route/auto_route.dart'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/models/albums/asset_selection_page_result.model.dart'; import 'package:immich_mobile/providers/asset_viewer/render_list.provider.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:isar/isar.dart'; @RoutePage() @@ -42,6 +43,9 @@ class AlbumAssetSelectionPage extends HookConsumerWidget { preselectedAssets: existingAssets, canDeselect: canDeselect, showMultiSelectIndicator: false, + onPopInvoked: (selectedAssets) { + _onPopInvoked(context, existingAssets, selectedAssets); + }, ); } @@ -51,7 +55,7 @@ class AlbumAssetSelectionPage extends HookConsumerWidget { leading: IconButton( icon: const Icon(Icons.close_rounded), onPressed: () { - AutoRouter.of(context).popForced(null); + _onPopInvoked(context, existingAssets, selected.value); }, ), title: selected.value.isEmpty @@ -88,4 +92,64 @@ class AlbumAssetSelectionPage extends HookConsumerWidget { ), ); } + + Future _showPopConfirmationDialog(BuildContext context) { + return showDialog( + context: context, + barrierDismissible: false, // user must tap button! + builder: (BuildContext context) { + return AlertDialog( + title: const Text('album_viewer_add_photos_exit_dialog_title').tr(), + content: + const Text('album_viewer_add_photos_exit_dialog_content').tr(), + actions: [ + TextButton( + onPressed: () => context.pop(false), + child: Text( + 'action_common_cancel', + style: TextStyle( + color: context.primaryColor, + fontWeight: FontWeight.bold, + ), + ).tr(), + ), + TextButton( + onPressed: () => context.pop(true), + child: Text( + 'action_common_confirm', + style: TextStyle( + color: context.primaryColor, + fontWeight: FontWeight.bold, + ), + ).tr(), + ), + ], + ); + }, + ); + } + + void _onPopInvoked( + BuildContext context, + Set existingAssets, + Set selectedAssets, + ) async { + if (!context.mounted) { + return; + } + + final equality = const SetEquality(); + final hasNotSelectedAssets = + equality.equals(selectedAssets, existingAssets); + + // Exit the page without warning if no new assets are selected + if (hasNotSelectedAssets) { + AutoRouter.of(context).popForced(); + } else { + final result = await _showPopConfirmationDialog(context); + if (result == true) { + AutoRouter.of(context).popForced(); + } + } + } } diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid.dart index 17872852e5af7..76ab9303f4e02 100644 --- a/mobile/lib/widgets/asset_grid/immich_asset_grid.dart +++ b/mobile/lib/widgets/asset_grid/immich_asset_grid.dart @@ -5,13 +5,13 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter_hooks/flutter_hooks.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/asyncvalue_extensions.dart'; +import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/render_list.provider.dart'; +import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_grid_data_structure.dart'; import 'package:immich_mobile/widgets/asset_grid/immich_asset_grid_view.dart'; -import 'package:immich_mobile/providers/app_settings.provider.dart'; -import 'package:immich_mobile/services/app_settings.service.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; class ImmichAssetGrid extends HookConsumerWidget { @@ -33,6 +33,7 @@ class ImmichAssetGrid extends HookConsumerWidget { final bool shrinkWrap; final bool showDragScroll; final bool showStack; + final void Function(Set selectedAssets)? onPopInvoked; const ImmichAssetGrid({ super.key, @@ -53,6 +54,7 @@ class ImmichAssetGrid extends HookConsumerWidget { this.shrinkWrap = false, this.showDragScroll = true, this.showStack = false, + this.onPopInvoked, }); @override @@ -119,6 +121,7 @@ class ImmichAssetGrid extends HookConsumerWidget { shrinkWrap: shrinkWrap, showDragScroll: showDragScroll, showStack: showStack, + onPopInvoked: onPopInvoked, ), ); } diff --git a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart index c38e61a47356d..55b91d1f92933 100644 --- a/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart +++ b/mobile/lib/widgets/asset_grid/immich_asset_grid_view.dart @@ -8,25 +8,25 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; +import 'package:fluttertoast/fluttertoast.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/extensions/build_context_extensions.dart'; import 'package:immich_mobile/extensions/collection_extensions.dart'; import 'package:immich_mobile/extensions/theme_extensions.dart'; import 'package:immich_mobile/providers/asset_viewer/current_asset.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/is_motion_video_playing.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/scroll_notifier.provider.dart'; +import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; import 'package:immich_mobile/providers/asset_viewer/show_controls.provider.dart'; +import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; +import 'package:immich_mobile/providers/tab.provider.dart'; +import 'package:immich_mobile/routing/router.dart'; import 'package:immich_mobile/widgets/asset_grid/asset_drag_region.dart'; +import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_image.dart'; import 'package:immich_mobile/widgets/asset_grid/thumbnail_placeholder.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; -import 'package:fluttertoast/fluttertoast.dart'; -import 'package:immich_mobile/widgets/asset_grid/control_bottom_app_bar.dart'; -import 'package:immich_mobile/routing/router.dart'; -import 'package:immich_mobile/entities/asset.entity.dart'; -import 'package:immich_mobile/providers/asset_viewer/scroll_to_date_notifier.provider.dart'; -import 'package:immich_mobile/providers/haptic_feedback.provider.dart'; -import 'package:immich_mobile/providers/tab.provider.dart'; import 'package:scrollable_positioned_list/scrollable_positioned_list.dart'; import 'asset_grid_data_structure.dart'; @@ -58,6 +58,7 @@ class ImmichAssetGridView extends ConsumerStatefulWidget { final bool shrinkWrap; final bool showDragScroll; final bool showStack; + final void Function(Set selectedAssets)? onPopInvoked; const ImmichAssetGridView({ super.key, @@ -78,6 +79,7 @@ class ImmichAssetGridView extends ConsumerStatefulWidget { this.shrinkWrap = false, this.showDragScroll = true, this.showStack = false, + this.onPopInvoked, }); @override @@ -542,7 +544,8 @@ class ImmichAssetGridViewState extends ConsumerState { Widget build(BuildContext context) { return PopScope( canPop: !(widget.selectionActive && _selectedAssets.isNotEmpty), - onPopInvokedWithResult: (didPop, _) => !didPop ? _deselectAll() : null, + onPopInvokedWithResult: (didPop, _) => + !didPop ? widget.onPopInvoked?.call(_selectedAssets) : null, child: Stack( children: [ AssetDragRegion(