Rich text editor for Flutter
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

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;
}
}