feat: delete the previous image when the cover changes in local mode (#6368)

* remove unecessary images from localstorage

* feat: Add handler for deleting previous cover image on cover image change

* fix: add local image case for versions after 0.5.5

* fix: add try catch block and delete action to bottom of function

* chore: add test case for uploading and deleting image in localmode

---------

Co-authored-by: Lucas.Xu <lucas.xu@appflowy.io>
This commit is contained in:
Ahad Patel 2024-12-31 07:06:14 +05:30 committed by GitHub
parent 73463cd7e3
commit b965a5f3ae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 99 additions and 3 deletions

View File

@ -1,15 +1,23 @@
import 'dart:io';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/header/document_cover_widget.dart';
import 'package:appflowy/plugins/document/presentation/editor_plugins/image/image_util.dart';
import 'package:appflowy/shared/icon_emoji_picker/flowy_icon_emoji_picker.dart';
import 'package:appflowy/shared/icon_emoji_picker/recent_icons.dart';
import 'package:appflowy_backend/protobuf/flowy-folder/view.pb.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_emoji_mart/flutter_emoji_mart.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
import '../../shared/emoji.dart';
import '../../shared/mock/mock_file_picker.dart';
import '../../shared/util.dart';
void main() {
@ -60,6 +68,59 @@ void main() {
tester.expectToSeeNoDocumentCover();
});
testWidgets('document cover local image tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();
tester.expectToSeeNoDocumentCover();
// Hover over cover toolbar to show 'Add Cover' and 'Add Icon' buttons
await tester.editor.hoverOnCoverToolbar();
// Insert a document cover
await tester.editor.tapOnAddCover();
tester.expectToSeeDocumentCover(CoverType.asset);
// Hover over the cover to show the 'Change Cover' and delete buttons
await tester.editor.hoverOnCover();
tester.expectChangeCoverAndDeleteButton();
// Change cover to a local image image
final imagePath = await rootBundle.load('assets/test/images/sample.jpeg');
final tempDirectory = await getTemporaryDirectory();
final localImagePath = p.join(tempDirectory.path, 'sample.jpeg');
final imageFile = File(localImagePath)
..writeAsBytesSync(imagePath.buffer.asUint8List());
await tester.editor.hoverOnCover();
await tester.editor.tapOnChangeCover();
final uploadButton = find.findTextInFlowyText(
LocaleKeys.document_imageBlock_upload_label.tr(),
);
await tester.tapButton(uploadButton);
mockPickFilePaths(paths: [localImagePath]);
await tester.tapButtonWithName(
LocaleKeys.document_imageBlock_upload_placeholder.tr(),
);
await tester.pumpAndSettle();
tester.expectToSeeDocumentCover(CoverType.file);
// Remove the cover
await tester.editor.hoverOnCover();
await tester.editor.tapOnRemoveCover();
tester.expectToSeeNoDocumentCover();
// Test if deleteImageFromLocalStorage(localImagePath) function is called once
await tester.pump(kDoubleTapTimeout);
expect(deleteImageTestCounter, 1);
// delete temp files
await imageFile.delete();
});
testWidgets('document icon tests', (tester) async {
await tester.initializeAppFlowy();
await tester.tapAnonymousSignInButton();

View File

@ -763,15 +763,29 @@ class DocumentCoverState extends State<DocumentCover> {
}
Future<void> onCoverChanged(CoverType type, String? details) async {
if (type == CoverType.file && details != null && !isURL(details)) {
final previousType = CoverType.fromString(
widget.node.attributes[DocumentHeaderBlockKeys.coverType],
);
final previousDetails =
widget.node.attributes[DocumentHeaderBlockKeys.coverDetails];
bool isFileType(CoverType type, String? details) =>
type == CoverType.file && details != null && !isURL(details);
if (isFileType(type, details)) {
if (_isLocalMode()) {
details = await saveImageToLocalStorage(details);
details = await saveImageToLocalStorage(details!);
} else {
// else we should save the image to cloud storage
(details, _) = await saveImageToCloudStorage(details, widget.view.id);
(details, _) = await saveImageToCloudStorage(details!, widget.view.id);
}
}
widget.onChangeCover(type, details);
// After cover change,delete from localstorage if previous cover was image type
if (isFileType(previousType, previousDetails) && _isLocalMode()) {
await deleteImageFromLocalStorage(previousDetails);
}
}
void setOverlayButtonsHidden(bool value) {

View File

@ -117,3 +117,16 @@ Future<List<ImageBlockData>> extractAndUploadImages(
return images;
}
@visibleForTesting
int deleteImageTestCounter = 0;
Future<void> deleteImageFromLocalStorage(String localImagePath) async {
try {
await File(localImagePath)
.delete()
.whenComplete(() => deleteImageTestCounter++);
} catch (e) {
Log.error('cannot delete image file', e);
}
}

View File

@ -226,6 +226,14 @@ class EditorMigration {
},
};
}
} else {
extra = {
ViewExtKeys.coverKey: {
ViewExtKeys.coverTypeKey:
PageStyleCoverImageType.localImage.toString(),
ViewExtKeys.coverValueKey: coverDetails,
},
};
}
break;
default: