Skip to content

Commit

Permalink
Add a event that triggers after removing the image from the editor &&…
Browse files Browse the repository at this point in the history
… delete unused dependencies and upgrade all packages and plugins and remove gallery_saver which has not been updated for more than 23 months, it was a great plugin but it old now, and I also add some simple documentation and other minor improvements
  • Loading branch information
EchoEllet committed Sep 29, 2023
1 parent 09113cb commit da0bcf5
Show file tree
Hide file tree
Showing 6 changed files with 259 additions and 120 deletions.
239 changes: 131 additions & 108 deletions flutter_quill_extensions/lib/embeds/builders.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import 'dart:io';
import 'dart:io' show File;

import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
Expand All @@ -7,20 +7,23 @@ import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.dart' as base;
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill/translations.dart';
import 'package:gal/gal.dart';
import 'package:http/http.dart' as http;
import 'package:math_keyboard/math_keyboard.dart';
import 'package:universal_html/html.dart' as html;

import '../shims/dart_ui_fake.dart'
if (dart.library.html) '../shims/dart_ui_real.dart' as ui;
if (dart.library.html) 'package:flutter_quill_extensions/shims/dart_ui_real.dart'
as ui;
import 'embed_types.dart';
import 'utils.dart';
import 'widgets/image.dart';
import 'widgets/image_resizer.dart';
import 'widgets/video_app.dart';
import 'widgets/youtube_video_app.dart';

class ImageEmbedBuilder extends EmbedBuilder {
ImageEmbedBuilder({required this.afterRemoveImageFromEditor});
final ImageEmbedBuilderAfterRemoveImageFromEditor afterRemoveImageFromEditor;

@override
String get key => BlockEmbed.imageType;

Expand All @@ -38,120 +41,130 @@ class ImageEmbedBuilder extends EmbedBuilder {
) {
assert(!kIsWeb, 'Please provide image EmbedBuilder for Web');

var image;
Widget image = const SizedBox.shrink();
final imageUrl = standardizeImageUrl(node.value.data);
OptionalSize? _imageSize;
OptionalSize? imageSize;
final style = node.style.attributes['style'];
if (base.isMobile() && style != null) {
final _attrs = base.parseKeyValuePairs(style.value.toString(), {
final attrs = base.parseKeyValuePairs(style.value.toString(), {
Attribute.mobileWidth,
Attribute.mobileHeight,
Attribute.mobileMargin,
Attribute.mobileAlignment
});
if (_attrs.isNotEmpty) {
if (attrs.isNotEmpty) {
assert(
_attrs[Attribute.mobileWidth] != null &&
_attrs[Attribute.mobileHeight] != null,
attrs[Attribute.mobileWidth] != null &&
attrs[Attribute.mobileHeight] != null,
'mobileWidth and mobileHeight must be specified');
final w = double.parse(_attrs[Attribute.mobileWidth]!);
final h = double.parse(_attrs[Attribute.mobileHeight]!);
_imageSize = OptionalSize(w, h);
final m = _attrs[Attribute.mobileMargin] == null
final w = double.parse(attrs[Attribute.mobileWidth]!);
final h = double.parse(attrs[Attribute.mobileHeight]!);
imageSize = OptionalSize(w, h);
final m = attrs[Attribute.mobileMargin] == null
? 0.0
: double.parse(_attrs[Attribute.mobileMargin]!);
final a = base.getAlignment(_attrs[Attribute.mobileAlignment]);
: double.parse(attrs[Attribute.mobileMargin]!);
final a = base.getAlignment(attrs[Attribute.mobileAlignment]);
image = Padding(
padding: EdgeInsets.all(m),
child: imageByUrl(imageUrl, width: w, height: h, alignment: a));
}
}

if (_imageSize == null) {
if (imageSize == null) {
image = imageByUrl(imageUrl);
_imageSize = OptionalSize((image as Image).width, image.height);
imageSize = OptionalSize((image as Image).width, image.height);
}

if (!readOnly && base.isMobile()) {
return GestureDetector(
onTap: () {
showDialog(
context: context,
builder: (context) {
final resizeOption = _SimpleDialogItem(
icon: Icons.settings_outlined,
color: Colors.lightBlueAccent,
text: 'Resize'.i18n,
onPressed: () {
Navigator.pop(context);
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
final _screenSize = MediaQuery.of(context).size;
return ImageResizer(
onImageResize: (w, h) {
final res = getEmbedNode(
controller, controller.selection.start);
final attr = base.replaceStyleString(
getImageStyleString(controller), w, h);
controller
..skipRequestKeyboard = true
..formatText(
res.offset, 1, StyleAttribute(attr));
},
imageWidth: _imageSize?.width,
imageHeight: _imageSize?.height,
maxWidth: _screenSize.width,
maxHeight: _screenSize.height);
});
},
);
final copyOption = _SimpleDialogItem(
icon: Icons.copy_all_outlined,
color: Colors.cyanAccent,
text: 'Copy'.i18n,
onPressed: () {
final imageNode =
getEmbedNode(controller, controller.selection.start)
.value;
final imageUrl = imageNode.value.data;
controller.copiedImageUrl =
ImageUrl(imageUrl, getImageStyleString(controller));
Navigator.pop(context);
},
);
final removeOption = _SimpleDialogItem(
icon: Icons.delete_forever_outlined,
color: Colors.red.shade200,
text: 'Remove'.i18n,
onPressed: () {
final offset =
getEmbedNode(controller, controller.selection.start)
.offset;
controller.replaceText(offset, 1, '',
TextSelection.collapsed(offset: offset));
Navigator.pop(context);
},
);
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
child: SimpleDialog(
shape: const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(10))),
children: [resizeOption, copyOption, removeOption]),
);
});
},
child: image);
onTap: () {
showDialog(
context: context,
builder: (context) {
final resizeOption = _SimpleDialogItem(
icon: Icons.settings_outlined,
color: Colors.lightBlueAccent,
text: 'Resize'.i18n,
onPressed: () {
Navigator.pop(context);
showCupertinoModalPopup<void>(
context: context,
builder: (context) {
final screenSize = MediaQuery.of(context).size;
return ImageResizer(
onImageResize: (w, h) {
final res = getEmbedNode(
controller, controller.selection.start);
final attr = base.replaceStyleString(
getImageStyleString(controller), w, h);
controller
..skipRequestKeyboard = true
..formatText(
res.offset, 1, StyleAttribute(attr));
},
imageWidth: imageSize?.width,
imageHeight: imageSize?.height,
maxWidth: screenSize.width,
maxHeight: screenSize.height);
});
},
);
final copyOption = _SimpleDialogItem(
icon: Icons.copy_all_outlined,
color: Colors.cyanAccent,
text: 'Copy'.i18n,
onPressed: () {
final imageNode =
getEmbedNode(controller, controller.selection.start)
.value;
final imageUrl = imageNode.value.data;
controller.copiedImageUrl =
ImageUrl(imageUrl, getImageStyleString(controller));
Navigator.pop(context);
},
);
final removeOption = _SimpleDialogItem(
icon: Icons.delete_forever_outlined,
color: Colors.red.shade200,
text: 'Remove'.i18n,
onPressed: () async {
final navigator = Navigator.of(context);
final offset =
getEmbedNode(controller, controller.selection.start)
.offset;
controller.replaceText(
offset,
1,
'',
TextSelection.collapsed(offset: offset),
);
navigator.pop();
await afterRemoveImageFromEditor(File(imageUrl));
},
);
return Padding(
padding: const EdgeInsets.fromLTRB(50, 0, 50, 0),
child: SimpleDialog(
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(10))),
children: [resizeOption, copyOption, removeOption]),
);
});
},
child: image,
);
}

if (!readOnly || !base.isMobile() || isImageBase64(imageUrl)) {
return image;
}

// We provide option menu for mobile platform excluding base64 image
return _menuOptionsForReadonlyImage(context, imageUrl, image);
return _menuOptionsForReadonlyImage(
context,
imageUrl,
image,
);
}
}

Expand Down Expand Up @@ -271,29 +284,39 @@ Widget _menuOptionsForReadonlyImage(
text: 'Save'.i18n,
onPressed: () async {
imageUrl = appendFileExtensionToImageUrl(imageUrl);
final messenger = ScaffoldMessenger.of(context);
Navigator.of(context).pop();

// Download image
final uri = Uri.parse(imageUrl);
final response = await http.get(uri);
if (response.statusCode != 200) {
throw Exception(
'failed to download image: ${response.statusCode}',
);
}
final saveImageResult = await saveImage(imageUrl);
final imageSavedSuccessfully = saveImageResult.isSuccess;

messenger.clearSnackBars();

// Save image to a temporary path
final fileName = uri.pathSegments.isEmpty ? 'image.jpg'
: uri.pathSegments.last;
final imagePath = '${Directory.systemTemp.path}/menu-opt-$fileName';
final imageFile = File(imagePath);
await imageFile.writeAsBytes(response.bodyBytes);
if (!imageSavedSuccessfully) {
// TODO: Please translate this
messenger.showSnackBar(const SnackBar(
content: Text(
'Error while saveing the image',
)));
return;
}

// Save image to gallery
await Gal.putImage(imagePath);
var message = 'Saved'.i18n;
switch (saveImageResult.method) {
// TODO: Please translate this too
case SaveImageResultMethod.network:
message += ' using the network.';
break;
case SaveImageResultMethod.localStorage:
message += ' using the local storage.';
break;
}

ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('Saved'.i18n)));
Navigator.pop(context);
messenger.showSnackBar(
SnackBar(
content: Text(message),
),
);
},
);
final zoomOption = _SimpleDialogItem(
Expand Down
3 changes: 3 additions & 0 deletions flutter_quill_extensions/lib/embeds/embed_types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,6 @@ class QuillFile {
final String path;
final Uint8List bytes;
}

typedef ImageEmbedBuilderAfterRemoveImageFromEditor = Future<void> Function(
File imageFile);
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ class LinkDialogState extends State<LinkDialog> {
super.initState();
_link = widget.link ?? '';
_controller = TextEditingController(text: _link);
// TODO: Consider replace the default Regex with this one
// Since that is not the reason I sent the changes then I will not edit it

// final defaultLinkNonSecureRegExp = RegExp(r'https?://.*?\.(?:png|jpe?g|gif|bmp|webp|tiff?)'); // Not secure
// final defaultLinkRegExp = RegExp(r'https://.*?\.(?:png|jpe?g|gif|bmp|webp|tiff?)'); // Secure
// _linkRegExp = widget.linkRegExp ?? defaultLinkRegExp;
_linkRegExp = widget.linkRegExp ?? AutoFormatMultipleLinksRule.linkRegExp;
}

Expand Down
Loading

0 comments on commit da0bcf5

Please sign in to comment.