From 3c5a29b0ab733bfebc2ed6d406476b927f87e386 Mon Sep 17 00:00:00 2001 From: Prateek Sunal Date: Mon, 21 Jul 2025 17:28:32 +0530 Subject: [PATCH] fix: popup menu item & smart people selection --- .../collections/album/smart_album_people.dart | 7 +- .../lib/ui/common/popup_item_async.dart | 50 ++++ .../gallery/gallery_app_bar_widget.dart | 282 +++++++++--------- 3 files changed, 192 insertions(+), 147 deletions(-) create mode 100644 mobile/apps/photos/lib/ui/common/popup_item_async.dart diff --git a/mobile/apps/photos/lib/ui/collections/album/smart_album_people.dart b/mobile/apps/photos/lib/ui/collections/album/smart_album_people.dart index 77d5fbe337..5e7f59f5bc 100644 --- a/mobile/apps/photos/lib/ui/collections/album/smart_album_people.dart +++ b/mobile/apps/photos/lib/ui/collections/album/smart_album_people.dart @@ -31,7 +31,6 @@ class SmartAlbumPeople extends StatefulWidget { class _SmartAlbumPeopleState extends State { final _selectedPeople = SelectedPeople(); SmartAlbumConfig? currentConfig; - bool isLoading = false; @override void initState() { @@ -69,10 +68,6 @@ class _SmartAlbumPeopleState extends State { labelText: S.of(context).save, shouldSurfaceExecutionStates: false, onTap: () async { - if (isLoading) return; - - isLoading = true; - final dialog = createProgressDialog( context, S.of(context).pleaseWait, @@ -151,9 +146,9 @@ class _SmartAlbumPeopleState extends State { await SmartAlbumsService.instance.saveConfig(newConfig); SmartAlbumsService.instance.syncSmartAlbums().ignore(); + await dialog.hide(); Navigator.pop(context); } catch (e) { - isLoading = false; await dialog.hide(); await showGenericErrorDialog(context: context, error: e); } diff --git a/mobile/apps/photos/lib/ui/common/popup_item_async.dart b/mobile/apps/photos/lib/ui/common/popup_item_async.dart new file mode 100644 index 0000000000..798c404dd7 --- /dev/null +++ b/mobile/apps/photos/lib/ui/common/popup_item_async.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; + +class EntePopupMenuItemAsync extends PopupMenuItem { + final String Function(U?) label; + final IconData Function(U?)? icon; + final Widget Function(U?)? iconWidget; + final Color? iconColor; + final Color? labelColor; + final Future Function()? future; + + EntePopupMenuItemAsync( + this.label, { + required T super.value, + this.icon, + this.iconWidget, + this.iconColor, + this.labelColor, + this.future, + super.key, + }) : assert( + icon != null || iconWidget != null, + 'Either icon or iconWidget must be provided.', + ), + assert( + !(icon != null && iconWidget != null), + 'Only one of icon or iconWidget can be provided.', + ), + super( + child: FutureBuilder( + future: future?.call(), + builder: (context, snapshot) { + return Row( + children: [ + if (iconWidget != null) + iconWidget(snapshot.data) + else if (icon != null) + Icon(icon(snapshot.data), color: iconColor), + const Padding( + padding: EdgeInsets.all(8), + ), + Text( + label(snapshot.data), + style: TextStyle(color: labelColor), + ), + ], + ); + }, + ), // Initially empty, will be populated in build + ); +} diff --git a/mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart b/mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart index c9f0bd55df..3383c58e86 100644 --- a/mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart +++ b/mobile/apps/photos/lib/ui/viewer/gallery/gallery_app_bar_widget.dart @@ -19,7 +19,6 @@ import "package:photos/l10n/l10n.dart"; import 'package:photos/models/backup_status.dart'; import "package:photos/models/button_result.dart"; import 'package:photos/models/collection/collection.dart'; -import "package:photos/models/collection/smart_album_config.dart"; import 'package:photos/models/device_collection.dart'; import "package:photos/models/file/file.dart"; import 'package:photos/models/gallery_type.dart'; @@ -36,6 +35,7 @@ import "package:photos/ui/cast/auto.dart"; import "package:photos/ui/cast/choose.dart"; import "package:photos/ui/collections/album/smart_album_people.dart"; import "package:photos/ui/common/popup_item.dart"; +import "package:photos/ui/common/popup_item_async.dart"; import "package:photos/ui/common/web_page.dart"; import 'package:photos/ui/components/action_sheet_widget.dart'; import 'package:photos/ui/components/buttons/button_widget.dart'; @@ -444,150 +444,150 @@ class _GalleryAppBarWidgetState extends State { } final bool isArchived = widget.collection?.isArchived() ?? false; final bool isHidden = widget.collection?.isHidden() ?? false; - List> items(SmartAlbumConfig? config) => - [ - if (galleryType.canRename()) - EntePopupMenuItem( - isQuickLink - ? S.of(context).convertToAlbum - : S.of(context).renameAlbum, - value: AlbumPopupAction.rename, - icon: isQuickLink ? Icons.photo_album_outlined : Icons.edit, - ), - if (galleryType.canSetCover()) - EntePopupMenuItem( - S.of(context).setCover, - value: AlbumPopupAction.setCover, - icon: Icons.image_outlined, - ), - if (galleryType.showMap()) - EntePopupMenuItem( - S.of(context).map, - value: AlbumPopupAction.map, - icon: Icons.map_outlined, - ), - if (galleryType.canSort()) - EntePopupMenuItem( - S.of(context).sortAlbumsBy, - value: AlbumPopupAction.sort, - icon: Icons.sort_outlined, - ), - if (galleryType == GalleryType.uncategorized) - EntePopupMenuItem( - S.of(context).cleanUncategorized, - value: AlbumPopupAction.cleanUncategorized, - icon: Icons.crop_original_outlined, - ), - if (galleryType.canPin()) - EntePopupMenuItem( - widget.collection!.isPinned - ? S.of(context).unpinAlbum - : S.of(context).pinAlbum, - value: AlbumPopupAction.pinAlbum, - iconWidget: widget.collection!.isPinned - ? const Icon(CupertinoIcons.pin_slash) - : Transform.rotate( - angle: 45 * math.pi / 180, // rotate by 45 degrees - child: const Icon(CupertinoIcons.pin), - ), - ), - if (galleryType == GalleryType.locationTag) - EntePopupMenuItem( - S.of(context).editLocation, - value: AlbumPopupAction.editLocation, - icon: Icons.edit_outlined, - ), - if (galleryType == GalleryType.locationTag) - EntePopupMenuItem( - S.of(context).deleteLocation, - value: AlbumPopupAction.deleteLocation, - icon: Icons.delete_outline, - iconColor: warning500, - labelColor: warning500, - ), - // Do not show archive option for favorite collection. If collection is - // already archived, allow user to unarchive that collection. - if (isArchived || (galleryType.canArchive() && !isHidden)) - EntePopupMenuItem( - value: AlbumPopupAction.ownedArchive, - isArchived - ? S.of(context).unarchiveAlbum - : S.of(context).archiveAlbum, - icon: isArchived ? Icons.unarchive : Icons.archive_outlined, - ), - if (!isArchived && galleryType.canHide()) - EntePopupMenuItem( - value: AlbumPopupAction.ownedHide, - isHidden ? S.of(context).unhide : S.of(context).hide, - icon: isHidden - ? Icons.visibility_outlined - : Icons.visibility_off_outlined, - ), - if (widget.collection != null) - EntePopupMenuItem( - value: AlbumPopupAction.playOnTv, - context.l10n.playOnTv, - icon: Icons.tv_outlined, - ), - if (widget.collection != null) - EntePopupMenuItem( - value: AlbumPopupAction.autoAddPhotos, - (config?.personIDs.isEmpty ?? true) - ? "Auto-add people" - : "Edit auto-add people", - icon: Icons.add, - ), - if (galleryType.canDelete()) - EntePopupMenuItem( - isQuickLink - ? S.of(context).removeLink - : S.of(context).deleteAlbum, - value: isQuickLink - ? AlbumPopupAction.removeLink - : AlbumPopupAction.delete, - icon: isQuickLink - ? Icons.remove_circle_outline - : Icons.delete_outline, - ), - if (galleryType == GalleryType.sharedCollection) - EntePopupMenuItem( - widget.collection!.hasShareeArchived() - ? S.of(context).unarchiveAlbum - : S.of(context).archiveAlbum, - value: AlbumPopupAction.sharedArchive, - icon: widget.collection!.hasShareeArchived() - ? Icons.unarchive - : Icons.archive_outlined, - ), - if (galleryType == GalleryType.sharedCollection) - EntePopupMenuItem( - S.of(context).leaveAlbum, - value: AlbumPopupAction.leave, - icon: Icons.logout, - ), - if (galleryType == GalleryType.localFolder) - EntePopupMenuItem( - S.of(context).freeUpDeviceSpace, - value: AlbumPopupAction.freeUpSpace, - icon: Icons.delete_sweep_outlined, - ), - if (galleryType == GalleryType.sharedPublicCollection && - widget.collection!.isDownloadEnabledForPublicLink()) - EntePopupMenuItem( - S.of(context).download, - value: AlbumPopupAction.downloadAlbum, - icon: Platform.isAndroid - ? Icons.download - : Icons.cloud_download_outlined, - ), - ]; + + final items = [ + if (galleryType.canRename()) + EntePopupMenuItem( + isQuickLink + ? S.of(context).convertToAlbum + : S.of(context).renameAlbum, + value: AlbumPopupAction.rename, + icon: isQuickLink ? Icons.photo_album_outlined : Icons.edit, + ), + if (galleryType.canSetCover()) + EntePopupMenuItem( + S.of(context).setCover, + value: AlbumPopupAction.setCover, + icon: Icons.image_outlined, + ), + if (galleryType.showMap()) + EntePopupMenuItem( + S.of(context).map, + value: AlbumPopupAction.map, + icon: Icons.map_outlined, + ), + if (galleryType.canSort()) + EntePopupMenuItem( + S.of(context).sortAlbumsBy, + value: AlbumPopupAction.sort, + icon: Icons.sort_outlined, + ), + if (galleryType == GalleryType.uncategorized) + EntePopupMenuItem( + S.of(context).cleanUncategorized, + value: AlbumPopupAction.cleanUncategorized, + icon: Icons.crop_original_outlined, + ), + if (galleryType.canPin()) + EntePopupMenuItem( + widget.collection!.isPinned + ? S.of(context).unpinAlbum + : S.of(context).pinAlbum, + value: AlbumPopupAction.pinAlbum, + iconWidget: widget.collection!.isPinned + ? const Icon(CupertinoIcons.pin_slash) + : Transform.rotate( + angle: 45 * math.pi / 180, // rotate by 45 degrees + child: const Icon(CupertinoIcons.pin), + ), + ), + if (galleryType == GalleryType.locationTag) + EntePopupMenuItem( + S.of(context).editLocation, + value: AlbumPopupAction.editLocation, + icon: Icons.edit_outlined, + ), + if (galleryType == GalleryType.locationTag) + EntePopupMenuItem( + S.of(context).deleteLocation, + value: AlbumPopupAction.deleteLocation, + icon: Icons.delete_outline, + iconColor: warning500, + labelColor: warning500, + ), + // Do not show archive option for favorite collection. If collection is + // already archived, allow user to unarchive that collection. + if (isArchived || (galleryType.canArchive() && !isHidden)) + EntePopupMenuItem( + value: AlbumPopupAction.ownedArchive, + isArchived + ? S.of(context).unarchiveAlbum + : S.of(context).archiveAlbum, + icon: isArchived ? Icons.unarchive : Icons.archive_outlined, + ), + if (!isArchived && galleryType.canHide()) + EntePopupMenuItem( + value: AlbumPopupAction.ownedHide, + isHidden ? S.of(context).unhide : S.of(context).hide, + icon: isHidden + ? Icons.visibility_outlined + : Icons.visibility_off_outlined, + ), + if (widget.collection != null) + EntePopupMenuItem( + value: AlbumPopupAction.playOnTv, + context.l10n.playOnTv, + icon: Icons.tv_outlined, + ), + if (widget.collection != null) + EntePopupMenuItemAsync( + (value) => (value?[widget.collection!.id]?.personIDs.isEmpty ?? true) + ? "Auto-add people" + : "Edit auto-add people", + value: AlbumPopupAction.autoAddPhotos, + future: SmartAlbumsService.instance.getSmartConfigs, + icon: (value) => Icons.add, + ), + if (galleryType.canDelete()) + EntePopupMenuItem( + isQuickLink ? S.of(context).removeLink : S.of(context).deleteAlbum, + value: isQuickLink + ? AlbumPopupAction.removeLink + : AlbumPopupAction.delete, + icon: + isQuickLink ? Icons.remove_circle_outline : Icons.delete_outline, + ), + if (galleryType == GalleryType.sharedCollection) + EntePopupMenuItem( + widget.collection!.hasShareeArchived() + ? S.of(context).unarchiveAlbum + : S.of(context).archiveAlbum, + value: AlbumPopupAction.sharedArchive, + icon: widget.collection!.hasShareeArchived() + ? Icons.unarchive + : Icons.archive_outlined, + ), + if (galleryType == GalleryType.sharedCollection) + EntePopupMenuItem( + S.of(context).leaveAlbum, + value: AlbumPopupAction.leave, + icon: Icons.logout, + ), + if (galleryType == GalleryType.localFolder) + EntePopupMenuItem( + S.of(context).freeUpDeviceSpace, + value: AlbumPopupAction.freeUpSpace, + icon: Icons.delete_sweep_outlined, + ), + if (galleryType == GalleryType.sharedPublicCollection && + widget.collection!.isDownloadEnabledForPublicLink()) + EntePopupMenuItem( + S.of(context).download, + value: AlbumPopupAction.downloadAlbum, + icon: Platform.isAndroid + ? Icons.download + : Icons.cloud_download_outlined, + ), + ]; + + if (items.isEmpty) { + return actions; + } actions.add( PopupMenuButton( itemBuilder: (context) { - final config = - SmartAlbumsService.instance.configs?[widget.collection?.id]; - return items(config); + return items; }, onSelected: (AlbumPopupAction value) async { if (value == AlbumPopupAction.rename) {