Merge pull request #879 from ente-io/delete_button

Memories FileViewer: Add Delete and Info button
This commit is contained in:
Neeraj Gupta 2023-02-22 16:12:23 +05:30 committed by GitHub
commit add5442607
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 224 additions and 137 deletions

View file

@ -1,8 +1,10 @@
import 'package:flutter/foundation.dart';
import 'package:logging/logging.dart';
import 'package:photos/core/constants.dart';
import "package:photos/core/event_bus.dart";
import 'package:photos/db/files_db.dart';
import 'package:photos/db/memories_db.dart';
import "package:photos/events/files_updated_event.dart";
import 'package:photos/models/filters/important_items_filter.dart';
import 'package:photos/models/memory.dart';
import 'package:photos/services/collections_service.dart';
@ -34,6 +36,17 @@ class MemoriesService extends ChangeNotifier {
DateTime.now().microsecondsSinceEpoch - (7 * microSecondsInDay),
);
});
Bus.instance.on<FilesUpdatedEvent>().where((event) {
return event.type == EventType.deletedFromEverywhere;
}).listen((event) {
final generatedIDs = event.updatedFiles
.where((element) => element.generatedID != null)
.map((e) => e.generatedID!)
.toSet();
_cachedMemories?.removeWhere((element) {
return generatedIDs.contains(element.file.generatedID);
});
});
}
void clearCache() {

View file

@ -0,0 +1,143 @@
import "package:flutter/cupertino.dart";
import "package:modal_bottom_sheet/modal_bottom_sheet.dart";
import "package:photos/models/file.dart";
import "package:photos/models/file_type.dart";
import "package:photos/theme/colors.dart";
import "package:photos/theme/ente_theme.dart";
import "package:photos/ui/components/action_sheet_widget.dart";
import "package:photos/ui/components/button_widget.dart";
import "package:photos/ui/components/models/button_type.dart";
import "package:photos/ui/viewer/file/file_info_widget.dart";
import "package:photos/utils/delete_file_util.dart";
import "package:photos/utils/dialog_util.dart";
import "package:photos/utils/toast_util.dart";
Future<void> showSingleFileDeleteSheet(
BuildContext context,
File file, {
Function(File)? onFileRemoved,
}) async {
final List<ButtonWidget> buttons = [];
final String fileType = file.fileType == FileType.video ? "video" : "photo";
final bool isBothLocalAndRemote =
file.uploadedFileID != null && file.localID != null;
final bool isLocalOnly = file.uploadedFileID == null && file.localID != null;
final bool isRemoteOnly = file.uploadedFileID != null && file.localID == null;
const String bodyHighlight = "It will be deleted from all albums.";
String body = "";
if (isBothLocalAndRemote) {
body = "This $fileType is in both ente and your device.";
} else if (isRemoteOnly) {
body = "This $fileType will be deleted from ente.";
} else if (isLocalOnly) {
body = "This $fileType will be deleted from your device.";
} else {
throw AssertionError("Unexpected state");
}
// Add option to delete from ente
if (isBothLocalAndRemote || isRemoteOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
if (isRemoteOnly) {
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
}
},
),
);
}
// Add option to delete from local
if (isBothLocalAndRemote || isLocalOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.second,
shouldSurfaceExecutionStates: false,
isInAlert: true,
onTap: () async {
await deleteFilesOnDeviceOnly(context, [file]);
if (isLocalOnly) {
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
}
},
),
);
}
if (isBothLocalAndRemote) {
buttons.add(
ButtonWidget(
labelText: "Delete from both",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.third,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
if (onFileRemoved != null) {
onFileRemoved(file);
}
},
),
);
}
buttons.add(
const ButtonWidget(
labelText: "Cancel",
buttonType: ButtonType.secondary,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.fourth,
isInAlert: true,
),
);
final actionResult = await showActionSheet(
context: context,
buttons: buttons,
actionSheetType: ActionSheetType.defaultActionSheet,
body: body,
bodyHighlight: bodyHighlight,
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
}
}
Future<void> showInfoSheet(BuildContext context, File file) async {
final colorScheme = getEnteColorScheme(context);
return showBarModalBottomSheet(
topControl: const SizedBox.shrink(),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
backgroundColor: colorScheme.backgroundElevated,
barrierColor: backdropFaintDark,
context: context,
builder: (BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: FileInfoWidget(file),
);
},
);
}

View file

@ -1,8 +1,11 @@
import "dart:io";
import "package:flutter/cupertino.dart";
import 'package:flutter/material.dart';
import 'package:photos/ente_theme_data.dart';
import 'package:photos/models/memory.dart';
import 'package:photos/services/memories_service.dart';
import 'package:photos/ui/extents_page_view.dart';
import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/viewer/file/file_widget.dart';
import 'package:photos/ui/viewer/file/thumbnail_widget.dart';
import 'package:photos/utils/date_time_util.dart';
@ -315,6 +318,22 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
);
}
void onFileDeleted() {
if (widget.memories.length == 1) {
Navigator.pop(context);
} else {
setState(() {
if (_index != 0) {
_pageController?.jumpToPage(_index - 1);
}
widget.memories.removeAt(_index);
if (_index != 0) {
_index--;
}
});
}
}
Hero _buildInfoText() {
return Hero(
tag: widget.title,
@ -346,17 +365,46 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildBottomIcons() {
final file = widget.memories[_index].file;
return Container(
alignment: Alignment.bottomRight,
padding: const EdgeInsets.fromLTRB(0, 0, 26, 20),
child: IconButton(
icon: Icon(
Icons.adaptive.share,
color: Colors.white, //same for both themes
return SafeArea(
child: Container(
alignment: Alignment.bottomRight,
padding: const EdgeInsets.fromLTRB(26, 0, 26, 20),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
IconButton(
icon: const Icon(
Icons.delete_outline,
color: Colors.white, //same for both themes
),
onPressed: () async {
await showSingleFileDeleteSheet(
context,
file,
onFileRemoved: (file) => {onFileDeleted()},
);
},
),
IconButton(
icon: Icon(
Platform.isAndroid ? Icons.info_outline : CupertinoIcons.info,
color: Colors.white, //same for both themes
),
onPressed: () {
showInfoSheet(context, file);
},
),
IconButton(
icon: Icon(
Icons.adaptive.share,
color: Colors.white, //same for both themes
),
onPressed: () {
share(context, [file]);
},
),
],
),
onPressed: () {
share(context, [file]);
},
),
);
}
@ -381,7 +429,7 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
Widget _buildSwiper() {
_pageController = PageController(initialPage: _index);
return ExtentsPageView.extents(
return PageView.builder(
itemBuilder: (BuildContext context, int index) {
if (index < widget.memories.length - 1) {
final nextFile = widget.memories[index + 1].file;
@ -405,7 +453,6 @@ class _FullScreenMemoryState extends State<FullScreenMemory> {
},
itemCount: widget.memories.length,
controller: _pageController,
extents: 1,
onPageChanged: (index) async {
await MemoriesService.instance.markMemoryAsSeen(widget.memories[index]);
if (mounted) {

View file

@ -51,7 +51,7 @@ class _FileSelectionActionWidgetState extends State<FileSelectionActionWidget> {
late CollectionActions collectionActions;
late bool isCollectionOwner;
// _cachedCollectionForSharedLink is primarly used to avoid creating duplicate
// _cachedCollectionForSharedLink is primarily used to avoid creating duplicate
// links if user keeps on creating Create link button after selecting
// few files. This link is reset on any selection changed;
Collection? _cachedCollectionForSharedLink;

View file

@ -21,13 +21,10 @@ import 'package:photos/services/favorites_service.dart';
import 'package:photos/services/hidden_service.dart';
import 'package:photos/services/ignored_files_service.dart';
import 'package:photos/services/local_sync_service.dart';
import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/common/progress_dialog.dart';
import 'package:photos/ui/components/action_sheet_widget.dart';
import 'package:photos/ui/components/button_widget.dart';
import 'package:photos/ui/components/models/button_type.dart';
import 'package:photos/ui/create_collection_sheet.dart';
import 'package:photos/ui/viewer/file/custom_app_bar.dart';
import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/file_util.dart';
import 'package:photos/utils/toast_util.dart';
@ -339,109 +336,11 @@ class FadingAppBarState extends State<FadingAppBar> {
}
Future<void> _showSingleFileDeleteSheet(File file) async {
final List<ButtonWidget> buttons = [];
final String fileType = file.fileType == FileType.video ? "video" : "photo";
final bool isBothLocalAndRemote =
file.uploadedFileID != null && file.localID != null;
final bool isLocalOnly =
file.uploadedFileID == null && file.localID != null;
final bool isRemoteOnly =
file.uploadedFileID != null && file.localID == null;
const String bodyHighlight = "It will be deleted from all albums.";
String body = "";
if (isBothLocalAndRemote) {
body = "This $fileType is in both ente and your device.";
} else if (isRemoteOnly) {
body = "This $fileType will be deleted from ente.";
} else if (isLocalOnly) {
body = "This $fileType will be deleted from your device.";
} else {
throw AssertionError("Unexpected state");
}
// Add option to delete from ente
if (isBothLocalAndRemote || isRemoteOnly) {
buttons.add(
ButtonWidget(
labelText: isBothLocalAndRemote ? "Delete from ente" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.first,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromRemoteOnly(context, [file]);
showShortToast(context, "Moved to trash");
if (isRemoteOnly) {
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
}
},
),
);
}
// Add option to delete from local
if (isBothLocalAndRemote || isLocalOnly) {
buttons.add(
ButtonWidget(
labelText:
isBothLocalAndRemote ? "Delete from device" : "Yes, delete",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.second,
shouldSurfaceExecutionStates: false,
isInAlert: true,
onTap: () async {
await deleteFilesOnDeviceOnly(context, [file]);
if (isLocalOnly) {
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
}
},
),
);
}
if (isBothLocalAndRemote) {
buttons.add(
ButtonWidget(
labelText: "Delete from both",
buttonType: ButtonType.neutral,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.third,
shouldSurfaceExecutionStates: true,
isInAlert: true,
onTap: () async {
await deleteFilesFromEverywhere(context, [file]);
Navigator.of(context, rootNavigator: true).pop();
widget.onFileRemoved(file);
},
),
);
}
buttons.add(
const ButtonWidget(
labelText: "Cancel",
buttonType: ButtonType.secondary,
buttonSize: ButtonSize.large,
shouldStickToDarkTheme: true,
buttonAction: ButtonAction.fourth,
isInAlert: true,
),
await showSingleFileDeleteSheet(
context,
file,
onFileRemoved: widget.onFileRemoved,
);
final actionResult = await showActionSheet(
context: context,
buttons: buttons,
actionSheetType: ActionSheetType.defaultActionSheet,
body: body,
bodyHighlight: bodyHighlight,
);
if (actionResult?.action != null &&
actionResult!.action == ButtonAction.error) {
showGenericErrorDialog(context: context);
}
}
Future<void> _download(File file) async {

View file

@ -2,7 +2,6 @@ import 'dart:io';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:modal_bottom_sheet/modal_bottom_sheet.dart';
import 'package:photos/core/configuration.dart';
import 'package:photos/models/file.dart';
import 'package:photos/models/file_type.dart';
@ -12,8 +11,8 @@ import 'package:photos/models/trash_file.dart';
import 'package:photos/services/collections_service.dart';
import 'package:photos/theme/colors.dart';
import 'package:photos/theme/ente_theme.dart';
import "package:photos/ui/actions/file/file_actions.dart";
import 'package:photos/ui/create_collection_sheet.dart';
import 'package:photos/ui/viewer/file/file_info_widget.dart';
import 'package:photos/utils/delete_file_util.dart';
import 'package:photos/utils/magic_util.dart';
import 'package:photos/utils/share_util.dart';
@ -273,20 +272,6 @@ class FadingBottomBarState extends State<FadingBottomBar> {
}
Future<void> _displayInfo(File file) async {
final colorScheme = getEnteColorScheme(context);
return showBarModalBottomSheet(
topControl: const SizedBox.shrink(),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(0)),
backgroundColor: colorScheme.backgroundElevated,
barrierColor: backdropFaintDark,
context: context,
builder: (BuildContext context) {
return Padding(
padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: FileInfoWidget(file),
);
},
);
await showInfoSheet(context, file);
}
}