dartlangeditorflutterflutter-appsflutter-examplesflutter-packageflutter-widgetquillquill-deltaquilljsreactquillrich-textrich-text-editorwysiwygwysiwyg-editor
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
281 lines
9.7 KiB
281 lines
9.7 KiB
import 'dart:io' show Directory, File, Platform; |
|
|
|
import 'package:flutter/foundation.dart' show kIsWeb; |
|
import 'package:flutter_quill/flutter_quill.dart' as quill; |
|
import 'package:flutter_quill/quill_delta.dart'; |
|
import 'package:path/path.dart' as path; |
|
|
|
typedef OnGenerateNewFileNameCallback = String Function( |
|
String currentFileName, |
|
String fileExt, |
|
); |
|
|
|
class QuillImageUtilities { |
|
const QuillImageUtilities({ |
|
required this.document, |
|
}); |
|
|
|
final quill.Document document; |
|
|
|
/// Private function that is throw an error if the platform is web |
|
static void _webIsNotSupported(String functionName) { |
|
if (kIsWeb) { |
|
throw UnsupportedError( |
|
'The static function "$functionName()"' |
|
' on class "QuillImageUtilities" is not supported in Web', |
|
); |
|
} |
|
} |
|
|
|
/// Saves a list of images to a specified directory. |
|
/// |
|
/// This function is designed to work efficiently on |
|
/// mobile platforms, but it can also be used on other platforms. |
|
/// But it's not supported on web for now |
|
/// |
|
/// When you have a list of cached image paths |
|
/// from a Quill document and you want to save them, |
|
/// you can use this function. |
|
/// It takes a list of image paths and copies each image to the specified |
|
/// directory. If the image |
|
/// path does not exist, it returns an |
|
/// empty string for that item. |
|
/// |
|
/// Make sure that the image paths provided in the [images] |
|
/// list exist, and handle the cases where images are not found accordingly. |
|
/// |
|
/// [images]: List of image paths to be saved. |
|
/// [deleteThePreviousImages]: Indicates whether to delete the |
|
/// original cached images after copying. |
|
/// [saveDirectory]: The directory where the images will be saved. |
|
/// [startOfEachFile]: Each file will have a name and it need to be unique |
|
/// but to make the file name is clear we will need a string represent |
|
/// the start of each file |
|
/// |
|
/// Returns a list of paths to the newly saved images. |
|
/// For images that do not exist, their paths are returned as empty strings. |
|
/// |
|
/// Example usage: |
|
/// ```dart |
|
/// final documentsDir = await getApplicationDocumentsDirectory(); |
|
/// final savedImagePaths = await saveImagesToDirectory( |
|
/// images: cachedImagePaths, |
|
/// deleteThePreviousImages: true, |
|
/// saveDirectory: documentsDir, |
|
/// startOfEachFile: 'quill-image-', // default |
|
/// ); |
|
/// ``` |
|
static Future<List<String>> saveImagesToDirectory({ |
|
required Iterable<String> images, |
|
required deleteThePreviousImages, |
|
required Directory saveDirectory, |
|
OnGenerateNewFileNameCallback? onGenerateNewFileName, |
|
}) async { |
|
_webIsNotSupported('saveImagesToDirectory'); |
|
final newImagesFutures = images.map((cachedImagePath) async { |
|
final previousImageFile = File(cachedImagePath); |
|
final isPreviousImageFileExists = await previousImageFile.exists(); |
|
|
|
if (!isPreviousImageFileExists) { |
|
return ''; |
|
} |
|
|
|
final newImageFileExtension = path.extension(cachedImagePath); // with dot |
|
|
|
final dateTimeString = DateTime.now().toIso8601String(); |
|
final newImageFileName = onGenerateNewFileName?.call( |
|
cachedImagePath, |
|
newImageFileExtension, |
|
) ?? |
|
'quill-image-$dateTimeString$newImageFileExtension'; |
|
final newImagePath = path.join(saveDirectory.path, newImageFileName); |
|
final newImageFile = await previousImageFile.copy(newImagePath); |
|
if (deleteThePreviousImages) { |
|
await previousImageFile.delete(); |
|
} |
|
return newImageFile.path; |
|
}); |
|
// Await for the saving process for each image |
|
final newImages = await Future.wait(newImagesFutures); |
|
return newImages; |
|
} |
|
|
|
/// Deletes all local images referenced in a Quill document. |
|
/// it's not supported on web for now |
|
/// |
|
/// Be **careful**, on desktop you should never delete user images. only if you |
|
/// are sure the image is saved in applicaton documents directory |
|
/// |
|
/// on mobile the app is sandboxed so you can't delete user images |
|
/// because it will be a copy of the image for the app |
|
/// so you should be safe |
|
/// |
|
/// This function removes local images from the |
|
/// file system that are referenced in the provided [document]. |
|
/// |
|
/// [document]: The Quill document from which images will be deleted. |
|
/// |
|
/// Throws an [Exception] if any errors occur during the deletion process. |
|
/// |
|
/// Example usage: |
|
/// ```dart |
|
/// try { |
|
/// await deleteAllLocalImagesOfDocument(myQuillDocument); |
|
/// } catch (e) { |
|
/// print('Error deleting local images: $e'); |
|
/// } |
|
/// ``` |
|
Future<void> deleteAllLocalImages() async { |
|
_webIsNotSupported('deleteAllLocalImagesOfDocument'); |
|
final imagesPaths = getImagesPathsFromDocument( |
|
onlyLocalImages: true, |
|
); |
|
for (final image in imagesPaths) { |
|
final imageFile = File(image); |
|
final fileExists = await imageFile.exists(); |
|
if (!fileExists) { |
|
return; |
|
} |
|
final deletedFile = await imageFile.delete(); |
|
final deletedFileStillExists = await deletedFile.exists(); |
|
if (deletedFileStillExists) { |
|
throw Exception( |
|
'We have successfully deleted the file and it is still exists!!', |
|
); |
|
} |
|
} |
|
} |
|
|
|
/// Retrieves paths to images embedded in a Quill document. |
|
/// |
|
/// it's not supported on web for now. |
|
/// This function parses the Document and returns a list of image paths. |
|
/// |
|
/// [document]: The Quill document from which image paths will be retrieved. |
|
/// [onlyLocalImages]: If `true`, |
|
/// only local (non-web-url) image paths will be included. |
|
/// |
|
/// Returns an iterable of image paths. |
|
/// |
|
/// Example usage: |
|
/// ```dart |
|
/// final quillDocument = _controller.document; |
|
/// final imagePaths |
|
/// = getImagesPathsFromDocument(quillDocument, onlyLocalImages: true); |
|
/// print('Image paths: $imagePaths'); |
|
/// ``` |
|
/// |
|
/// Note: This function assumes that images are |
|
/// embedded as block embeds in the Quill document. |
|
Iterable<String> getImagesPathsFromDocument({ |
|
required bool onlyLocalImages, |
|
}) { |
|
_webIsNotSupported('getImagesPathsFromDocument'); |
|
// final images = document.root.children |
|
// .whereType<quill.Line>() |
|
// .where((node) { |
|
// if (node.isEmpty) { |
|
// return false; |
|
// } |
|
// final firstNode = node.children.first; |
|
// if (firstNode is! quill.Embed) { |
|
// return false; |
|
// } |
|
|
|
// if (firstNode.value.type != quill.BlockEmbed.imageType) { |
|
// return false; |
|
// } |
|
// final imageSource = firstNode.value.data; |
|
// if (imageSource is! String) { |
|
// return false; |
|
// } |
|
// if (onlyLocalImages && isHttpBasedUrl(imageSource)) { |
|
// return false; |
|
// } |
|
// return imageSource.trim().isNotEmpty; |
|
// }) |
|
// .toList() |
|
// .map((e) => (e.children.first as quill.Embed).value.data as String); |
|
|
|
final images = <String>[]; |
|
for (final item in document.toDelta().toJson()) { |
|
if (!item.containsKey(Operation.insertKey)) { |
|
return []; |
|
} |
|
final insertValue = item[Operation.insertKey]; |
|
|
|
// Check if the insert value is a map with the "image" key |
|
if (insertValue is Map && |
|
insertValue.containsKey(quill.BlockEmbed.imageType)) { |
|
final String imageUrl = insertValue[quill.BlockEmbed.imageType]; |
|
images.add(imageUrl); |
|
} |
|
} |
|
return images; |
|
} |
|
|
|
/// Determines if an image file is cached based on the platform. |
|
/// it's not supported on web for now |
|
/// |
|
/// On mobile platforms (Android and iOS), images are typically |
|
/// cached in temporary directories. |
|
/// This function helps identify whether the given image file path |
|
/// is a cached path on supported platforms. |
|
/// |
|
/// [imagePath] is the path of the image file to check for caching. |
|
/// |
|
/// Returns `true` if the image is cached, `false` otherwise. |
|
/// On other platforms it will always return false |
|
static bool isImageCached(String imagePath) { |
|
// Determine if the image path is a cached path based on platform |
|
if (kIsWeb) { |
|
// For now this will not work for web |
|
return false; |
|
} |
|
if (Platform.isAndroid) { |
|
return imagePath.contains('cache'); |
|
} |
|
if (Platform.isIOS) { |
|
// Don't use isAppleOS() since macOS has different behavior |
|
return imagePath.contains('tmp'); |
|
} |
|
// On other platforms like desktop |
|
// The image is not cached and we will get a direct |
|
// access to the image |
|
return false; |
|
} |
|
|
|
/// Retrieves cached image paths from a Quill document, |
|
/// primarily for mobile platforms. |
|
/// |
|
/// it's not supported on web for now |
|
/// |
|
/// This function scans a Quill document to identify |
|
/// and return paths to locally cached images. |
|
/// It is specifically designed for mobile |
|
/// operating systems (Android and iOS). |
|
/// |
|
/// |
|
/// [replaceUnexistentImagesWith] is an optional parameter. |
|
/// If provided, it replaces non-existent image paths |
|
/// with the specified value. If not provided, non-existent |
|
/// image paths are removed from the result. |
|
/// |
|
/// Returns a list of cached image paths found in the document. |
|
/// On non-mobile platforms, this function returns an empty list. |
|
Iterable<String> getCachedImagePathsFromDocument({ |
|
String? replaceUnexistentImagesWith, |
|
}) { |
|
_webIsNotSupported('getCachedImagePathsFromDocument'); |
|
final imagePaths = getImagesPathsFromDocument( |
|
onlyLocalImages: true, |
|
); |
|
|
|
// We don't want the not cached images to be saved again for example. |
|
final cachesImagePaths = imagePaths.where((imagePath) { |
|
final isCurrentImageCached = isImageCached(imagePath); |
|
return isCurrentImageCached; |
|
}).toList(); |
|
return cachesImagePaths; |
|
} |
|
}
|
|
|