New selection overlay for trash screen

This commit is contained in:
ashilkn 2023-01-27 19:26:29 +05:30
parent af5841204d
commit 0f570d1c62
5 changed files with 68 additions and 633 deletions

View file

@ -184,4 +184,12 @@ extension GalleyTypeExtension on GalleryType {
bool showUnFavoriteOption() { bool showUnFavoriteOption() {
return this == GalleryType.favorite; return this == GalleryType.favorite;
} }
bool showRestoreOption() {
return this == GalleryType.trash;
}
bool showPermanentlyDeleteOption() {
return this == GalleryType.trash;
}
} }

View file

@ -233,6 +233,28 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
); );
} }
if (widget.type.showRestoreOption()) {
secondList.add(
BlurMenuItemWidget(
leadingIcon: Icons.visibility,
labelText: "Restore",
menuItemColor: colorScheme.fillFaint,
onTap: _restore,
),
);
}
if (widget.type.showPermanentlyDeleteOption()) {
secondList.add(
BlurMenuItemWidget(
leadingIcon: Icons.delete_forever_outlined,
labelText: "Permanently delete now",
menuItemColor: colorScheme.fillFaint,
onTap: _permanentlyDelete,
),
);
}
if (firstList.isNotEmpty || secondList.isNotEmpty) { if (firstList.isNotEmpty || secondList.isNotEmpty) {
if (firstList.isNotEmpty) { if (firstList.isNotEmpty) {
items.add(firstList); items.add(firstList);
@ -420,4 +442,22 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
showShortToast(context, "Link copied to clipboard"); showShortToast(context, "Link copied to clipboard");
} }
} }
void _restore() {
createCollectionSheet(
widget.selectedFiles,
null,
context,
actionType: CollectionActionType.restoreFiles,
);
}
Future<void> _permanentlyDelete() async {
if (await deleteFromTrash(
context,
widget.selectedFiles.files.toList(),
)) {
widget.selectedFiles.clearAll();
}
}
} }

View file

@ -95,11 +95,28 @@ class _FileSelectionOverlayBarState extends State<FileSelectionOverlayBar> {
), ),
); );
} }
if (widget.galleryType == GalleryType.trash) {
iconsButton.add(
IconButtonWidget(
icon: Icons.delete_forever_outlined,
iconButtonType: IconButtonType.primary,
iconColor: iconColor,
onTap: () async {
if (await deleteFromTrash(
context,
widget.selectedFiles.files.toList(),
)) {
widget.selectedFiles.clearAll();
}
},
),
);
}
iconsButton.add( iconsButton.add(
IconButtonWidget( IconButtonWidget(
icon: Icons.adaptive.share_outlined, icon: Icons.adaptive.share_outlined,
iconButtonType: IconButtonType.primary, iconButtonType: IconButtonType.primary,
iconColor: getEnteColorScheme(context).blurStrokeBase, iconColor: iconColor,
onTap: () => shareSelected( onTap: () => shareSelected(
context, context,
shareButtonKey, shareButtonKey,

View file

@ -1,627 +0,0 @@
import 'dart:async';
import 'dart:io';
import 'dart:ui';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/core/event_bus.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/events/subscription_purchased_event.dart';
import 'package:photos/models/collection.dart';
import 'package:photos/models/gallery_type.dart';
import 'package:photos/models/magic_metadata.dart';
import 'package:photos/models/selected_files.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/services/hidden_service.dart';
import 'package:photos/ui/create_collection_sheet.dart';
import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/magic_util.dart';
import 'package:photos/utils/share_util.dart';
import 'package:photos/utils/toast_util.dart';
class GalleryOverlayWidget extends StatefulWidget {
final GalleryType type;
final SelectedFiles selectedFiles;
final String? path;
final Collection? collection;
const GalleryOverlayWidget(
this.type,
this.selectedFiles, {
this.path,
this.collection,
Key? key,
}) : super(key: key);
@override
State<GalleryOverlayWidget> createState() => _GalleryOverlayWidgetState();
}
class _GalleryOverlayWidgetState extends State<GalleryOverlayWidget> {
late StreamSubscription _userAuthEventSubscription;
late Function() _selectedFilesListener;
final GlobalKey shareButtonKey = GlobalKey();
@override
void initState() {
_selectedFilesListener = () {
setState(() {});
};
widget.selectedFiles.addListener(_selectedFilesListener);
_userAuthEventSubscription =
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_userAuthEventSubscription.cancel();
widget.selectedFiles.removeListener(_selectedFilesListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
final bool filesAreSelected = widget.selectedFiles.files.isNotEmpty;
final bottomPadding = Platform.isAndroid ? 0.0 : 12.0;
return Padding(
padding: EdgeInsets.only(bottom: bottomPadding),
child: AnimatedContainer(
duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut,
height: filesAreSelected ? 108 : 0,
child: AnimatedOpacity(
duration: const Duration(milliseconds: 100),
opacity: filesAreSelected ? 1.0 : 0.0,
curve: Curves.easeIn,
child: IgnorePointer(
ignoring: !filesAreSelected,
child: OverlayWidget(
widget.type,
widget.selectedFiles,
path: widget.path,
collection: widget.collection,
),
),
),
),
);
}
}
class OverlayWidget extends StatefulWidget {
final GalleryType type;
final SelectedFiles selectedFiles;
final String? path;
final Collection? collection;
const OverlayWidget(
this.type,
this.selectedFiles, {
this.path,
this.collection,
Key? key,
}) : super(key: key);
@override
State<OverlayWidget> createState() => _OverlayWidgetState();
}
class _OverlayWidgetState extends State<OverlayWidget> {
final _logger = Logger("GalleryOverlay");
late StreamSubscription _userAuthEventSubscription;
late Function() _selectedFilesListener;
final GlobalKey shareButtonKey = GlobalKey();
@override
void initState() {
_selectedFilesListener = () {
setState(() {});
};
widget.selectedFiles.addListener(_selectedFilesListener);
_userAuthEventSubscription =
Bus.instance.on<SubscriptionPurchasedEvent>().listen((event) {
setState(() {});
});
super.initState();
}
@override
void dispose() {
_userAuthEventSubscription.cancel();
widget.selectedFiles.removeListener(_selectedFilesListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
color: Colors.transparent,
child: ListView(
//ListView is for animation to work without render overflow
physics: const NeverScrollableScrollPhysics(),
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12),
child: Container(
decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)),
child: ClipRRect(
borderRadius: BorderRadius.circular(8),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: Container(
color: Theme.of(context)
.colorScheme
.frostyBlurBackdropFilterColor,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Padding(
padding: const EdgeInsets.fromLTRB(13, 13, 0, 13),
child: Text(
widget.selectedFiles.files.length.toString() +
' selected',
style: Theme.of(context)
.textTheme
.subtitle2!
.copyWith(
fontWeight: FontWeight.w600,
color:
Theme.of(context).colorScheme.iconColor,
),
),
),
Row(
children: _getActions(context),
)
],
),
),
),
),
),
),
const Padding(padding: EdgeInsets.symmetric(vertical: 8)),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(24),
child: GestureDetector(
onTap: _clearSelectedFiles,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 50, sigmaY: 50),
child: Container(
padding: const EdgeInsets.symmetric(vertical: 8),
//height: 32,
width: 86,
color: Theme.of(context)
.colorScheme
.frostyBlurBackdropFilterColor,
child: Center(
child: Text(
'Cancel',
style: Theme.of(context)
.textTheme
.subtitle2!
.copyWith(
fontWeight: FontWeight.w600,
color: Theme.of(context).colorScheme.iconColor,
),
),
),
),
),
),
),
],
),
],
),
);
}
void _clearSelectedFiles() {
widget.selectedFiles.clearAll();
}
List<Widget> _getActions(BuildContext context) {
final List<Widget> actions = <Widget>[];
if (widget.type == GalleryType.trash) {
_addTrashAction(actions);
return actions;
}
// skip add button for incoming collection till this feature is implemented
if (Configuration.instance.hasConfiguredAccount() &&
widget.type != GalleryType.sharedCollection &&
widget.type != GalleryType.hidden) {
IconData iconData = Platform.isAndroid ? Icons.add : CupertinoIcons.add;
// show upload icon instead of add for files selected in local gallery
if (widget.type == GalleryType.localFolder) {
iconData = Icons.cloud_upload_outlined;
}
actions.add(
Tooltip(
message: "add",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(iconData),
onPressed: () async {
await onActionSelected("add");
},
),
),
);
}
if (Configuration.instance.hasConfiguredAccount() &&
widget.type == GalleryType.ownedCollection &&
widget.collection!.type != CollectionType.favorites) {
actions.add(
Tooltip(
message: "Move",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
Platform.isAndroid
? Icons.arrow_forward
: CupertinoIcons.arrow_right,
),
onPressed: () {
onActionSelected('move');
},
),
),
);
}
actions.add(
Tooltip(
message: "Share",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
key: shareButtonKey,
icon: Icon(Platform.isAndroid ? Icons.share : CupertinoIcons.share),
onPressed: () {
_shareSelected(context);
},
),
),
);
if (widget.type == GalleryType.homepage ||
widget.type == GalleryType.archive ||
widget.type == GalleryType.hidden ||
widget.type == GalleryType.localFolder ||
widget.type == GalleryType.searchResults) {
actions.add(
Tooltip(
message: "Delete",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon:
Icon(Platform.isAndroid ? Icons.delete : CupertinoIcons.delete),
onPressed: () {
_showDeleteSheet(context);
},
),
),
);
} else if (widget.type == GalleryType.ownedCollection) {
if (widget.collection!.type == CollectionType.folder) {
actions.add(
Tooltip(
message: "Delete",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: Icon(
Platform.isAndroid ? Icons.delete : CupertinoIcons.delete,
),
onPressed: () {
_showDeleteSheet(context);
},
),
),
);
} else {
actions.add(
Tooltip(
message: "Remove",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: const Icon(
Icons.remove_circle_rounded,
),
onPressed: () {
_showRemoveFromCollectionSheet(context);
},
),
),
);
}
}
if (widget.type == GalleryType.homepage ||
widget.type == GalleryType.archive) {
final bool showArchive = widget.type == GalleryType.homepage;
if (showArchive) {
actions.add(
Tooltip(
message: 'Archive',
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: const Icon(
Icons.archive_outlined,
),
onPressed: () {
onActionSelected('archive');
},
),
),
);
} else {
actions.insert(
0,
Tooltip(
message: 'Unarchive',
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: const Icon(
Icons.unarchive,
),
onPressed: () {
onActionSelected('unarchive');
},
),
),
);
}
}
return actions;
}
Future<void> onActionSelected(String value) async {
debugPrint("Action Selected $value");
switch (value.toLowerCase()) {
case 'hide':
await _handleHideRequest(context);
break;
case 'archive':
await _handleVisibilityChangeRequest(context, visibilityArchive);
break;
case 'unarchive':
await _handleVisibilityChangeRequest(context, visibilityVisible);
break;
default:
break;
}
}
void _addTrashAction(List<Widget> actions) {
actions.add(
Tooltip(
message: "Restore",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: const Icon(
Icons.restore,
),
onPressed: () {
createCollectionSheet(
widget.selectedFiles,
null,
context,
actionType: CollectionActionType.restoreFiles,
);
},
),
),
);
actions.add(
Tooltip(
message: "Delete permanently",
child: IconButton(
color: Theme.of(context).colorScheme.iconColor,
icon: const Icon(
Icons.delete_forever,
),
onPressed: () async {
if (await deleteFromTrash(
context,
widget.selectedFiles.files.toList(),
)) {
_clearSelectedFiles();
}
},
),
),
);
}
Future<void> _handleVisibilityChangeRequest(
BuildContext context,
int newVisibility,
) async {
try {
await changeVisibility(
context,
widget.selectedFiles.files.toList(),
newVisibility,
);
} catch (e, s) {
_logger.severe("failed to update file visibility", e, s);
await showGenericErrorDialog(context: context);
} finally {
_clearSelectedFiles();
}
}
// note: Keeping this method here so that it can be used whenever we move to
// to bottom UI
Future<void> _handleHideRequest(BuildContext context) async {
try {
final hideResult = await CollectionsService.instance
.hideFiles(context, widget.selectedFiles.files.toList());
if (hideResult) {
_clearSelectedFiles();
}
} catch (e, s) {
_logger.severe("failed to update file visibility", e, s);
await showGenericErrorDialog(context: context);
}
}
void _shareSelected(BuildContext context) {
share(
context,
widget.selectedFiles.files.toList(),
shareButtonKey: shareButtonKey,
);
}
void _showDeleteSheet(BuildContext context) {
final count = widget.selectedFiles.files.length;
bool containsUploadedFile = false, containsLocalFile = false;
for (final file in widget.selectedFiles.files) {
if (file.uploadedFileID != null) {
containsUploadedFile = true;
}
if (file.localID != null) {
containsLocalFile = true;
}
}
final actions = <Widget>[];
if (containsUploadedFile && containsLocalFile) {
actions.add(
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesOnDeviceOnly(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
showToast(context, "Files deleted from device");
},
child: const Text("Device"),
),
);
actions.add(
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromRemoteOnly(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
showShortToast(context, "Moved to trash");
},
child: const Text("ente"),
),
);
actions.add(
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
},
child: const Text("Everywhere"),
),
);
} else {
actions.add(
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
await deleteFilesFromEverywhere(
context,
widget.selectedFiles.files.toList(),
);
_clearSelectedFiles();
},
child: const Text("Delete"),
),
);
}
final action = CupertinoActionSheet(
title: Text(
"Delete " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
(containsUploadedFile && containsLocalFile ? " from" : "?"),
),
actions: actions,
cancelButton: CupertinoActionSheetAction(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
);
showCupertinoModalPopup(
context: context,
builder: (_) => action,
barrierColor: Colors.black.withOpacity(0.75),
);
}
void _showRemoveFromCollectionSheet(BuildContext context) {
final count = widget.selectedFiles.files.length;
final action = CupertinoActionSheet(
title: Text(
"Remove " +
count.toString() +
" file" +
(count == 1 ? "" : "s") +
" from " +
widget.collection!.name! +
"?",
),
actions: <Widget>[
CupertinoActionSheetAction(
isDestructiveAction: true,
onPressed: () async {
Navigator.of(context, rootNavigator: true).pop();
final dialog = createProgressDialog(context, "Removing files...");
await dialog.show();
try {
await CollectionsService.instance.removeFromCollection(
widget.collection!.id,
widget.selectedFiles.files.toList(),
);
await dialog.hide();
widget.selectedFiles.clearAll();
} catch (e, s) {
_logger.severe(e, s);
await dialog.hide();
showGenericErrorDialog(context: context);
}
},
child: const Text("Remove"),
),
],
cancelButton: CupertinoActionSheetAction(
child: const Text("Cancel"),
onPressed: () {
Navigator.of(context, rootNavigator: true).pop();
},
),
);
showCupertinoModalPopup(context: context, builder: (_) => action);
}
}

View file

@ -9,9 +9,9 @@ import 'package:photos/events/force_reload_trash_page_event.dart';
import 'package:photos/models/gallery_type.dart'; import 'package:photos/models/gallery_type.dart';
import 'package:photos/models/selected_files.dart'; import 'package:photos/models/selected_files.dart';
import 'package:photos/ui/common/bottom_shadow.dart'; import 'package:photos/ui/common/bottom_shadow.dart';
import 'package:photos/ui/viewer/actions/file_selection_overlay_bar.dart';
import 'package:photos/ui/viewer/gallery/gallery.dart'; import 'package:photos/ui/viewer/gallery/gallery.dart';
import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart'; import 'package:photos/ui/viewer/gallery/gallery_app_bar_widget.dart';
import 'package:photos/ui/viewer/gallery/gallery_overlay_widget.dart';
import 'package:photos/utils/delete_file_util.dart'; import 'package:photos/utils/delete_file_util.dart';
class TrashPage extends StatefulWidget { class TrashPage extends StatefulWidget {
@ -109,10 +109,7 @@ class _TrashPageState extends State<TrashPage> {
), ),
), ),
), ),
GalleryOverlayWidget( FileSelectionOverlayBar(GalleryType.trash, widget._selectedFiles)
widget.overlayType,
widget._selectedFiles,
)
], ],
), ),
); );