diff --git a/app_flowy/packages/flowy_editor/example/assets/block_document.fdoc b/app_flowy/packages/flowy_editor/example/assets/block_document.fdoc index a8ab042949..1f0ad033ad 100644 --- a/app_flowy/packages/flowy_editor/example/assets/block_document.fdoc +++ b/app_flowy/packages/flowy_editor/example/assets/block_document.fdoc @@ -13,7 +13,18 @@ "flutter_logo": "" }, "attributes":{ - "size": 50.0 + "size": 200.0 + } + }, + { + "insert":"\n" + }, + { + "insert": { + "test_block_type": "test_data" + }, + "attributes":{ + } }, { diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/builder.dart b/app_flowy/packages/flowy_editor/lib/src/widget/builder.dart index 58a6fa009b..0b9e3ec679 100644 --- a/app_flowy/packages/flowy_editor/lib/src/widget/builder.dart +++ b/app_flowy/packages/flowy_editor/lib/src/widget/builder.dart @@ -1,12 +1,6 @@ -import 'dart:convert'; -import 'dart:io' as io; - -import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; -import 'package:string_validator/string_validator.dart'; -import '../model/document/node/leaf.dart'; import '../widget/raw_editor.dart'; import '../widget/selection.dart'; import '../rendering/editor.dart'; diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/editor.dart b/app_flowy/packages/flowy_editor/lib/src/widget/editor.dart index 43ca613f70..bec98d78f4 100644 --- a/app_flowy/packages/flowy_editor/lib/src/widget/editor.dart +++ b/app_flowy/packages/flowy_editor/lib/src/widget/editor.dart @@ -1,22 +1,18 @@ -import 'dart:io' as io; -import 'dart:convert'; import 'dart:ui'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:url_launcher/url_launcher.dart'; -import 'package:string_validator/string_validator.dart'; import '../widget/raw_editor.dart'; import '../widget/builder.dart'; +import '../widget/embed.dart'; import '../widget/proxy.dart'; -import '../widget/image_viewer_screen.dart'; import '../model/document/attribute.dart'; import '../model/document/document.dart'; -import '../model/document/node/embed.dart'; import '../model/document/node/line.dart'; import '../model/document/node/container.dart' as container_node; -import '../model/document/node/leaf.dart' as leaf; +import '../model/document/node/leaf.dart' show Leaf; import '../service/controller.dart'; import '../service/cursor.dart'; import '../service/style.dart'; @@ -61,13 +57,13 @@ class FlowyEditor extends StatefulWidget { this.textCapitalization = TextCapitalization.sentences, this.keyboardAppearance = Brightness.light, this.scrollPhysics, - this.embedBuilder = EmbedBuilder.defaultBuilder, this.onLaunchUrl, this.onTapDown, this.onTapUp, this.onLongPressStart, this.onLongPressMoveUpdate, this.onLongPressEnd, + this.embedProvider = EmbedBaseProvider.buildEmbedWidget, }); factory FlowyEditor.basic({ @@ -105,7 +101,7 @@ class FlowyEditor extends StatefulWidget { final TextCapitalization textCapitalization; final Brightness keyboardAppearance; final ScrollPhysics? scrollPhysics; - final EmbedBuilderFuncion embedBuilder; + final EmbedBuilderFuncion embedProvider; // Callback @@ -222,7 +218,7 @@ class _FlowyEditorState extends State implements EditorTextSelectio widget.keyboardAppearance, widget.enableInteractiveSelection, widget.scrollPhysics, - widget.embedBuilder, + widget.embedProvider, ), ); } @@ -429,7 +425,7 @@ class _FlowyEditorSelectionGestureDetectorBuilder extends EditorTextSelectionGes } // Link - final segment = segmentResult.node as leaf.Leaf; + final segment = segmentResult.node as Leaf; if (segment.style.containsKey(Attribute.link.key)) { var launchUrl = getEditor()!.widget.onLaunchUrl; launchUrl ??= _launchUrl; @@ -444,27 +440,6 @@ class _FlowyEditorSelectionGestureDetectorBuilder extends EditorTextSelectionGes return false; } - // Image - if (getEditor()!.widget.readOnly && segment.value is BlockEmbed) { - final blockEmbed = segment.value as BlockEmbed; - if (blockEmbed.type == 'image') { - final imageUrl = EmbedBuilder.standardizeImageUrl(blockEmbed.data); - Navigator.push( - getEditor()!.context, - MaterialPageRoute(builder: (context) { - return ImageTapWrapper( - imageProvider: imageUrl.startsWith('http') - ? NetworkImage(imageUrl) - : isBase64(imageUrl) - ? Image.memory(base64.decode(imageUrl)) as ImageProvider? - : FileImage(io.File(imageUrl)), - ); - }), - ); - } - return false; - } - // Fallback if (_flipListCheckbox(position, line, segmentResult)) { return true; diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/embed.dart b/app_flowy/packages/flowy_editor/lib/src/widget/embed.dart index 3448d473f2..c773ada2ea 100644 --- a/app_flowy/packages/flowy_editor/lib/src/widget/embed.dart +++ b/app_flowy/packages/flowy_editor/lib/src/widget/embed.dart @@ -1,78 +1,38 @@ -import 'dart:convert'; -import 'dart:io' as io; - +import 'package:flowy_editor/src/widget/embed_builder/logo_builder.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:string_validator/string_validator.dart'; +import '../widget/embed_builder/image_builder.dart'; import '../model/document/node/leaf.dart' show Embed; abstract class EmbedWidgetBuilder { + const EmbedWidgetBuilder(); + bool canHandle(String type); Widget? buildeWidget(BuildContext context, Embed node); } /* ---------------------------------- Embed --------------------------------- */ - -class EmbedBuilder { - static const kImageTypeKey = 'image'; +class EmbedBaseProvider { static const kFlutterLogoTypeKey = 'flutter_logo'; - static const builtInTypes = [kImageTypeKey, kFlutterLogoTypeKey]; + static const builtInProviders = [ + ImageEmbedBuilder(), + LogoEmbedBuilder(), + ]; - static Widget defaultBuilder(BuildContext context, Embed node) { - assert(!kIsWeb, 'Please provide EmbedBuilder for Web'); - switch (node.value.type) { - case kImageTypeKey: - return _generateImageEmbed(context, node); - case kFlutterLogoTypeKey: - return _generateFlutterLogoEmbed(context, node); - default: - return Align( - alignment: Alignment.center, - child: _UnsupportedHintBlock(node), - ); + static Widget buildEmbedWidget(BuildContext context, Embed node) { + Widget? result; + for (final builder in builtInProviders) { + if (builder.canHandle(node.value.type)) { + result = builder.buildeWidget(context, node); + if (result != null) { + break; + } + } } - } - - // Generator - - static Widget _generateImageEmbed(BuildContext context, Embed node) { - final imageUrl = standardizeImageUrl(node.value.data); - return imageUrl.startsWith('http') - ? Image.network(imageUrl) - : isBase64(imageUrl) - ? Image.memory(base64.decode(imageUrl)) - : Image.file(io.File(imageUrl)); - } - - static Widget _generateFlutterLogoEmbed(BuildContext context, Embed node) { - final size = node.style.attributes['size']; - var logoSize = size != null ? size.value as double? ?? 100.0 : 100.0; - return Align( - alignment: Alignment.center, - child: Container( - width: logoSize, - height: logoSize, - color: Colors.red, - child: GestureDetector( - onTap: () { - print('Flutter logo tapped'); - }, - child: FlutterLogo(size: logoSize), - ), - ), - ); - } - - // Helper - - static String standardizeImageUrl(String url) { - if (url.contains('base64')) { - return url.split(',')[1]; - } - return url; + return result ?? Align(alignment: Alignment.center, child: _UnsupportedHintBlock(node)); } } diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/image_builder.dart b/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/image_builder.dart new file mode 100644 index 0000000000..2db62c3571 --- /dev/null +++ b/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/image_builder.dart @@ -0,0 +1,52 @@ +import 'dart:convert'; +import 'dart:io' as io; + +import 'package:flutter/src/widgets/framework.dart'; +import 'package:flutter/material.dart'; +import 'package:string_validator/string_validator.dart'; + +import '../../model/document/node/leaf.dart' show Embed; +import '../embed.dart'; + +/* --------------------------------- Sample --------------------------------- */ + +/// +/// { +/// "insert": { +/// "image": "https://test.com/sample.png" +/// }, +/// "attributes" : { +/// "width": "100.0" +/// } +/// } +/// + +/* --------------------------------- Builder -------------------------------- */ + +class ImageEmbedBuilder extends EmbedWidgetBuilder { + const ImageEmbedBuilder() : super(); + + static const kImageTypeKey = 'image'; + + @override + bool canHandle(String type) { + return type == kImageTypeKey; + } + + @override + Widget? buildeWidget(BuildContext context, Embed node) { + final imageUrl = _standardizeImageUrl(node.value.data); + return imageUrl.startsWith('http') + ? Image.network(imageUrl) + : isBase64(imageUrl) + ? Image.memory(base64.decode(imageUrl)) + : Image.file(io.File(imageUrl)); + } + + String _standardizeImageUrl(String url) { + if (url.contains('base64')) { + return url.split(',')[1]; + } + return url; + } +} diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/logo_builder.dart b/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/logo_builder.dart new file mode 100644 index 0000000000..4df82ad4e8 --- /dev/null +++ b/app_flowy/packages/flowy_editor/lib/src/widget/embed_builder/logo_builder.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/src/widgets/framework.dart'; + +import '../../model/document/node/leaf.dart'; +import '../../widget/embed.dart'; + +/* --------------------------------- Sample --------------------------------- */ + +/// +/// { +/// "insert": { +/// "flutter_logo": "" +/// }, +/// "attributes" : { +/// "size": 100.0 +/// } +/// } +/// + +/* --------------------------------- Builder -------------------------------- */ + +class LogoEmbedBuilder extends EmbedWidgetBuilder { + const LogoEmbedBuilder() : super(); + + static const kImageTypeKey = 'flutter_logo'; + + @override + bool canHandle(String type) { + return type == kImageTypeKey; + } + + @override + Widget? buildeWidget(BuildContext context, Embed node) { + final size = node.style.attributes['size']; + var logoSize = size != null ? size.value as double? ?? 100.0 : 100.0; + return Align( + alignment: Alignment.center, + child: Container( + width: logoSize, + height: logoSize, + child: GestureDetector( + onTap: () { + print('Flutter logo tapped'); + }, + child: FlutterLogo(size: logoSize), + ), + ), + ); + } +} diff --git a/app_flowy/packages/flowy_editor/lib/src/widget/proxy.dart b/app_flowy/packages/flowy_editor/lib/src/widget/proxy.dart index 736eefa2b3..eb72b20e48 100644 --- a/app_flowy/packages/flowy_editor/lib/src/widget/proxy.dart +++ b/app_flowy/packages/flowy_editor/lib/src/widget/proxy.dart @@ -22,8 +22,7 @@ class BaselineProxy extends SingleChildRenderObjectWidget { } @override - void updateRenderObject( - BuildContext context, covariant RenderBaselineProxy renderObject) { + void updateRenderObject(BuildContext context, covariant RenderBaselineProxy renderObject) { renderObject ..textStyle = textStyle! ..padding = padding!; @@ -38,8 +37,7 @@ class EmbedProxy extends SingleChildRenderObjectWidget { const EmbedProxy(Widget child) : super(child: child); @override - RenderEmbedProxy createRenderObject(BuildContext context) => - RenderEmbedProxy(null); + RenderEmbedProxy createRenderObject(BuildContext context) => RenderEmbedProxy(null); } /* ---------------------------------- Text ---------------------------------- */ @@ -82,8 +80,7 @@ class RichTextProxy extends SingleChildRenderObjectWidget { } @override - void updateRenderObject( - BuildContext context, covariant RenderParagraphProxy renderObject) { + void updateRenderObject(BuildContext context, covariant RenderParagraphProxy renderObject) { renderObject ..textStyle = textStyle ..textAlign = textAlign