Selaa lähdekoodia

[Fix] delete cover image document update (#2224)

* fix: reset cover on deleting selected cover image

* feat: add alert dialog on delete

* fix: update cover image list when file not found

* fix: missing const in change cover popup

* fix: added dialog string in translation file

* fix: analytical issue in changeCoverPopover

* fix: added trailing commas in change_cover_popover
GouravShDev 2 vuotta sitten
vanhempi
commit
01ced3bdc0

+ 3 - 1
frontend/appflowy_flutter/assets/translations/en.json

@@ -384,7 +384,9 @@
         "pickFromFiles": "Pick from files",
         "couldNotFetchImage": "Could not fetch image",
         "imageSavingFailed": "Image Saving Failed",
-        "addIcon": "Add Icon"
+        "addIcon": "Add Icon",
+        "coverRemoveAlert" : "It will be removed from cover after it is deleted.",
+        "alertDialogConfirmation" : "Are you sure, you want to continue?"
       }
     }
   },

+ 110 - 11
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/change_cover_popover.dart

@@ -79,8 +79,10 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
   @override
   Widget build(BuildContext context) {
     return BlocProvider(
-      create: (context) => ChangeCoverPopoverBloc()
-        ..add(const ChangeCoverPopoverEvent.fetchPickedImagePaths()),
+      create: (context) => ChangeCoverPopoverBloc(
+        editorState: widget.editorState,
+        node: widget.node,
+      )..add(const ChangeCoverPopoverEvent.fetchPickedImagePaths()),
       child: BlocBuilder<ChangeCoverPopoverBloc, ChangeCoverPopoverState>(
         builder: (context, state) {
           return Padding(
@@ -149,10 +151,31 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
               hoverColor: Theme.of(context).colorScheme.secondaryContainer,
               LocaleKeys.document_plugins_cover_clearAll.tr(),
               fontColor: Theme.of(context).colorScheme.tertiary,
-              onPressed: () {
-                context
-                    .read<ChangeCoverPopoverBloc>()
-                    .add(const ChangeCoverPopoverEvent.clearAllImages());
+              onPressed: () async {
+                final hasFileImageCover = CoverSelectionType.fromString(
+                      widget.node.attributes[kCoverSelectionTypeAttribute],
+                    ) ==
+                    CoverSelectionType.file;
+                final changeCoverBloc = context.read<ChangeCoverPopoverBloc>();
+                if (hasFileImageCover) {
+                  await showDialog(
+                    context: context,
+                    builder: (context) {
+                      return DeleteImageAlertDialog(
+                        onSubmit: () {
+                          changeCoverBloc.add(
+                            const ChangeCoverPopoverEvent.clearAllImages(),
+                          );
+                          Navigator.pop(context);
+                        },
+                      );
+                    },
+                  );
+                } else {
+                  context
+                      .read<ChangeCoverPopoverBloc>()
+                      .add(const ChangeCoverPopoverEvent.clearAllImages());
+                }
               },
               mainAxisAlignment: MainAxisAlignment.end,
             ),
@@ -263,6 +286,32 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
                     images[index - 1],
                   );
                 },
+                onImageDelete: () async {
+                  final changeCoverBloc =
+                      context.read<ChangeCoverPopoverBloc>();
+                  final deletingCurrentCover =
+                      widget.node.attributes[kCoverSelectionAttribute] ==
+                          images[index - 1];
+                  if (deletingCurrentCover) {
+                    await showDialog(
+                      context: context,
+                      builder: (context) {
+                        return DeleteImageAlertDialog(
+                          onSubmit: () {
+                            changeCoverBloc.add(
+                              ChangeCoverPopoverEvent.deleteImage(
+                                images[index - 1],
+                              ),
+                            );
+                            Navigator.pop(context);
+                          },
+                        );
+                      },
+                    );
+                  } else {
+                    changeCoverBloc.add(DeleteImage(images[index - 1]));
+                  }
+                },
                 imagePath: images[index - 1],
               );
             },
@@ -285,14 +334,68 @@ class _ChangeCoverPopoverState extends State<ChangeCoverPopover> {
   }
 }
 
+class DeleteImageAlertDialog extends StatelessWidget {
+  const DeleteImageAlertDialog({
+    Key? key,
+    required this.onSubmit,
+  }) : super(key: key);
+
+  final Function() onSubmit;
+
+  @override
+  Widget build(BuildContext context) {
+    return AlertDialog(
+      title: FlowyText.semibold(
+        "Image is used in cover",
+        fontSize: 20,
+        color: Theme.of(context).colorScheme.tertiary,
+      ),
+      content: Container(
+        constraints: const BoxConstraints(minHeight: 100),
+        padding: const EdgeInsets.symmetric(
+          vertical: 20,
+        ),
+        child: Column(
+          mainAxisSize: MainAxisSize.min,
+          children: [
+            const Text(LocaleKeys.document_plugins_cover_coverRemoveAlert).tr(),
+            const SizedBox(
+              height: 4,
+            ),
+            const Text(
+              LocaleKeys.document_plugins_cover_alertDialogConfirmation,
+            ).tr(),
+          ],
+        ),
+      ),
+      contentPadding: const EdgeInsets.symmetric(
+        vertical: 10.0,
+        horizontal: 20.0,
+      ),
+      actions: [
+        TextButton(
+          onPressed: () => Navigator.pop(context),
+          child: const Text(LocaleKeys.button_Cancel).tr(),
+        ),
+        TextButton(
+          onPressed: onSubmit,
+          child: const Text(LocaleKeys.button_OK).tr(),
+        ),
+      ],
+    );
+  }
+}
+
 class ImageGridItem extends StatefulWidget {
   const ImageGridItem({
     Key? key,
     required this.onImageSelect,
+    required this.onImageDelete,
     required this.imagePath,
   }) : super(key: key);
 
   final Function() onImageSelect;
+  final Function() onImageDelete;
   final String imagePath;
 
   @override
@@ -343,11 +446,7 @@ class _ImageGridItemState extends State<ImageGridItem> {
                   'editor/delete',
                   color: Theme.of(context).colorScheme.tertiary,
                 ),
-                onPressed: () {
-                  context.read<ChangeCoverPopoverBloc>().add(
-                        ChangeCoverPopoverEvent.deleteImage(widget.imagePath),
-                      );
-                },
+                onPressed: widget.onImageDelete,
               ),
             ),
         ],

+ 27 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/change_cover_popover_bloc.dart

@@ -2,6 +2,8 @@ import 'dart:async';
 import 'dart:io';
 
 import 'package:appflowy/plugins/document/presentation/plugins/cover/change_cover_popover.dart';
+import 'package:appflowy/plugins/document/presentation/plugins/cover/cover_node_widget.dart';
+import 'package:appflowy_editor/appflowy_editor.dart';
 import 'package:flutter_bloc/flutter_bloc.dart';
 import 'package:freezed_annotation/freezed_annotation.dart';
 import 'package:shared_preferences/shared_preferences.dart';
@@ -10,9 +12,12 @@ part 'change_cover_popover_bloc.freezed.dart';
 
 class ChangeCoverPopoverBloc
     extends Bloc<ChangeCoverPopoverEvent, ChangeCoverPopoverState> {
+  final EditorState editorState;
+  final Node node;
   late final SharedPreferences _prefs;
   final _initCompleter = Completer<void>();
-  ChangeCoverPopoverBloc() : super(const ChangeCoverPopoverState.initial()) {
+  ChangeCoverPopoverBloc({required this.editorState, required this.node})
+      : super(const ChangeCoverPopoverState.initial()) {
     SharedPreferences.getInstance().then((prefs) {
       _prefs = prefs;
       _initCompleter.complete();
@@ -26,8 +31,13 @@ class ChangeCoverPopoverBloc
         },
         deleteImage: (DeleteImage deleteImage) async {
           final currentState = state;
+          final currentlySelectedImage =
+              node.attributes[kCoverSelectionAttribute];
           if (currentState is Loaded) {
             await _deleteImageInStorage(deleteImage.path);
+            if (currentlySelectedImage == deleteImage.path) {
+              _removeCoverImageFromNode();
+            }
             final updateImageList = currentState.imageNames
                 .where((path) => path != deleteImage.path)
                 .toList();
@@ -37,9 +47,15 @@ class ChangeCoverPopoverBloc
         },
         clearAllImages: (ClearAllImages clearAllImages) async {
           final currentState = state;
+          final currentlySelectedImage =
+              node.attributes[kCoverSelectionAttribute];
+
           if (currentState is Loaded) {
             for (final image in currentState.imageNames) {
               await _deleteImageInStorage(image);
+              if (currentlySelectedImage == image) {
+                _removeCoverImageFromNode();
+              }
             }
             await _updateImagePathsInStorage([]);
             emit(const Loaded([]));
@@ -56,6 +72,7 @@ class ChangeCoverPopoverBloc
       return imageNames;
     }
     imageNames.removeWhere((name) => !File(name).existsSync());
+    _prefs.setStringList(kLocalImagesKey, imageNames);
     return imageNames;
   }
 
@@ -69,6 +86,15 @@ class ChangeCoverPopoverBloc
     final imageFile = File(path);
     await imageFile.delete();
   }
+
+  Future<void> _removeCoverImageFromNode() async {
+    final transaction = editorState.transaction;
+    transaction.updateNode(node, {
+      kCoverSelectionTypeAttribute: CoverSelectionType.initial.toString(),
+      kIconSelectionAttribute: node.attributes[kIconSelectionAttribute]
+    });
+    return editorState.apply(transaction);
+  }
 }
 
 @freezed

+ 11 - 1
frontend/appflowy_flutter/lib/plugins/document/presentation/plugins/cover/cover_node_widget.dart

@@ -443,8 +443,18 @@ class _CoverImageState extends State<_CoverImage> {
     final Widget coverImage;
     switch (selectionType) {
       case CoverSelectionType.file:
+        final imageFile =
+            File(widget.node.attributes[kCoverSelectionAttribute]);
+        if (!imageFile.existsSync()) {
+          // reset cover state
+          WidgetsBinding.instance.addPostFrameCallback((_) {
+            widget.onCoverChanged(CoverSelectionType.initial, null);
+          });
+          coverImage = const SizedBox();
+          break;
+        }
         coverImage = Image.file(
-          File(widget.node.attributes[kCoverSelectionAttribute]),
+          imageFile,
           fit: BoxFit.cover,
         );
         break;