diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index ab352b58..8c654718 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -6,23 +6,16 @@ import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter/services.dart'; -import 'package:gallery_saver/gallery_saver.dart'; import '../models/documents/document.dart'; import '../models/documents/nodes/container.dart' as container_node; -import '../models/documents/nodes/leaf.dart' as leaf; import '../models/documents/nodes/line.dart'; -import '../utils/platform_helper.dart'; -import '../utils/simple_dialog_item.dart'; -import '../utils/string_helper.dart'; import 'box.dart'; import 'controller.dart'; import 'cursor.dart'; import 'default_styles.dart'; import 'delegate.dart'; -import 'embeds/image.dart'; -import 'embeds/video_app.dart'; -import 'embeds/youtube_video_app.dart'; +import 'embeds/default_embed_builder.dart'; import 'float_cursor.dart'; import 'link.dart'; import 'raw_editor.dart'; @@ -156,101 +149,6 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics { void selectPosition({required SelectionChangedCause cause}); } -String _standardizeImageUrl(String url) { - if (url.contains('base64')) { - return url.split(',')[1]; - } - return url; -} - -Widget defaultEmbedBuilder( - BuildContext context, leaf.Embed node, bool readOnly) { - assert(!kIsWeb, 'Please provide EmbedBuilder for Web'); - switch (node.value.type) { - case 'image': - final imageUrl = _standardizeImageUrl(node.value.data); - var image; - final style = node.style.attributes['style']; - if (isMobile() && style != null) { - final _attrs = parseKeyValuePairs(style.value.toString(), - {'mobileWidth', 'mobileHeight', 'mobileMargin', 'mobileAlignment'}); - if (_attrs.isNotEmpty) { - assert( - _attrs['mobileWidth'] != null && _attrs['mobileHeight'] != null, - 'mobileWidth and mobileHeight must be specified'); - final w = double.parse(_attrs['mobileWidth']!); - final h = double.parse(_attrs['mobileHeight']!); - final m = _attrs['mobileMargin'] == null - ? 0.0 - : double.parse(_attrs['mobileMargin']!); - final a = getAlignment(_attrs['mobileAlignment']); - image = Padding( - padding: EdgeInsets.all(m), - child: imageByUrl(imageUrl, width: w, height: h, alignment: a)); - } - } - image ??= imageByUrl(imageUrl); - - if (!readOnly || !isMobile() || isImageBase64(imageUrl)) { - return image; - } - - /// We provide option menu only for mobile platform excluding base64 - return GestureDetector( - onTap: () { - showDialog( - context: context, - builder: (context) => Padding( - padding: const EdgeInsets.fromLTRB(50, 0, 50, 0), - child: SimpleDialog( - shape: const RoundedRectangleBorder( - borderRadius: - BorderRadius.all(Radius.circular(10))), - children: [ - SimpleDialogItem( - icon: Icons.save, - color: Colors.greenAccent, - text: 'Save', - onPressed: () { - // TODO: improve this - GallerySaver.saveImage(imageUrl).then((_) => - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('Saved')))); - }, - ), - SimpleDialogItem( - icon: Icons.zoom_in, - color: Colors.cyanAccent, - text: 'Zoom', - onPressed: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (context) => ImageTapWrapper( - imageUrl: imageUrl))); - }, - ) - ]), - )); - }, - child: image); - case 'video': - final videoUrl = node.value.data; - if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { - return YoutubeVideoApp( - videoUrl: videoUrl, context: context, readOnly: readOnly); - } - return VideoApp(videoUrl: videoUrl, context: context, readOnly: readOnly); - default: - throw UnimplementedError( - 'Embeddable type "${node.value.type}" is not supported by default ' - 'embed builder of QuillEditor. You must pass your own builder function ' - 'to embedBuilder property of QuillEditor or QuillField widgets.', - ); - } -} - class QuillEditor extends StatefulWidget { const QuillEditor( {required this.controller, diff --git a/lib/src/widgets/embeds/default_embed_builder.dart b/lib/src/widgets/embeds/default_embed_builder.dart new file mode 100644 index 00000000..596c3523 --- /dev/null +++ b/lib/src/widgets/embeds/default_embed_builder.dart @@ -0,0 +1,99 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:gallery_saver/gallery_saver.dart'; + +import '../../../widgets/image.dart'; +import '../../models/documents/nodes/leaf.dart' as leaf; +import '../../utils/platform_helper.dart'; +import '../../utils/simple_dialog_item.dart'; +import '../../utils/string_helper.dart'; +import 'video_app.dart'; +import 'youtube_video_app.dart'; + +Widget defaultEmbedBuilder( + BuildContext context, leaf.Embed node, bool readOnly) { + assert(!kIsWeb, 'Please provide EmbedBuilder for Web'); + switch (node.value.type) { + case 'image': + final imageUrl = standardizeImageUrl(node.value.data); + var image; + final style = node.style.attributes['style']; + if (isMobile() && style != null) { + final _attrs = parseKeyValuePairs(style.value.toString(), + {'mobileWidth', 'mobileHeight', 'mobileMargin', 'mobileAlignment'}); + if (_attrs.isNotEmpty) { + assert( + _attrs['mobileWidth'] != null && _attrs['mobileHeight'] != null, + 'mobileWidth and mobileHeight must be specified'); + final w = double.parse(_attrs['mobileWidth']!); + final h = double.parse(_attrs['mobileHeight']!); + final m = _attrs['mobileMargin'] == null + ? 0.0 + : double.parse(_attrs['mobileMargin']!); + final a = getAlignment(_attrs['mobileAlignment']); + image = Padding( + padding: EdgeInsets.all(m), + child: imageByUrl(imageUrl, width: w, height: h, alignment: a)); + } + } + image ??= imageByUrl(imageUrl); + + if (!readOnly || !isMobile() || isImageBase64(imageUrl)) { + return image; + } + + /// We provide option menu only for mobile platform excluding base64 + return GestureDetector( + onTap: () { + showDialog( + context: context, + builder: (context) => Padding( + padding: const EdgeInsets.fromLTRB(50, 0, 50, 0), + child: SimpleDialog( + shape: const RoundedRectangleBorder( + borderRadius: + BorderRadius.all(Radius.circular(10))), + children: [ + SimpleDialogItem( + icon: Icons.save, + color: Colors.greenAccent, + text: 'Save', + onPressed: () { + // TODO: improve this + GallerySaver.saveImage(imageUrl).then((_) => + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('Saved')))); + }, + ), + SimpleDialogItem( + icon: Icons.zoom_in, + color: Colors.cyanAccent, + text: 'Zoom', + onPressed: () { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => ImageTapWrapper( + imageUrl: imageUrl))); + }, + ) + ]), + )); + }, + child: image); + case 'video': + final videoUrl = node.value.data; + if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { + return YoutubeVideoApp( + videoUrl: videoUrl, context: context, readOnly: readOnly); + } + return VideoApp(videoUrl: videoUrl, context: context, readOnly: readOnly); + default: + throw UnimplementedError( + 'Embeddable type "${node.value.type}" is not supported by default ' + 'embed builder of QuillEditor. You must pass your own builder function ' + 'to embedBuilder property of QuillEditor or QuillField widgets.', + ); + } +} diff --git a/lib/src/widgets/embeds/image.dart b/lib/src/widgets/embeds/image.dart index ae801bc7..38ade21b 100644 --- a/lib/src/widgets/embeds/image.dart +++ b/lib/src/widgets/embeds/image.dart @@ -26,6 +26,13 @@ Widget imageByUrl(String imageUrl, width: width, height: height, alignment: alignment); } +String standardizeImageUrl(String url) { + if (url.contains('base64')) { + return url.split(',')[1]; + } + return url; +} + class ImageTapWrapper extends StatelessWidget { const ImageTapWrapper({ required this.imageUrl, diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor.dart index 83aa5740..9e500019 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor.dart @@ -21,6 +21,7 @@ import 'cursor.dart'; import 'default_styles.dart'; import 'delegate.dart'; import 'editor.dart'; +import 'embeds/default_embed_builder.dart'; import 'keyboard_listener.dart'; import 'link.dart'; import 'proxy.dart';