Merge pull request #879 from ente-io/delete_button
Memories FileViewer: Add Delete and Info button
This commit is contained in:
commit
add5442607
6 changed files with 224 additions and 137 deletions
|
@ -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() {
|
||||
|
|
143
lib/ui/actions/file/file_actions.dart
Normal file
143
lib/ui/actions/file/file_actions.dart
Normal 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),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue