allow widgets to override widget span properties (#1141)

pull/1142/head
Bertrand 2 years ago committed by GitHub
parent 34cba178f4
commit b7951b02c9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 3
      README.md
  2. 3
      doc_cn.md
  3. 3
      example/lib/pages/home_page.dart
  4. 14
      example/lib/universal_ui/universal_ui.dart
  5. 9
      flutter_quill_extensions/lib/embeds/builders.dart
  6. 8
      lib/src/widgets/delegate.dart
  7. 38
      lib/src/widgets/editor.dart
  8. 6
      lib/src/widgets/embeds.dart
  9. 56
      lib/src/widgets/text_line.dart

@ -241,7 +241,7 @@ After that, we need to map this "notes" type into a widget. In that case, I used
Don't forget to add this method to the `QuillEditor` after that!
```dart
class NotesEmbedBuilder implements EmbedBuilder {
class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
@ -255,6 +255,7 @@ class NotesEmbedBuilder implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
bool inline,
) {
final notes = NotesBlockEmbed(node.value.data).document;

@ -246,7 +246,7 @@ class NotesBlockEmbed extends CustomBlockEmbed {
在这里我们使用 `ListTile` 来渲染它,并使用 `onTap` 方法来编辑内容,最后不要忘记将此方法添加到 `QuillEditor`
```dart
class NotesEmbedBuilder implements EmbedBuilder {
class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
@ -260,6 +260,7 @@ class NotesEmbedBuilder implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
bool inline,
) {
final notes = NotesBlockEmbed(node.value.data).document;

@ -487,7 +487,7 @@ class _HomePageState extends State<HomePage> {
}
}
class NotesEmbedBuilder implements EmbedBuilder {
class NotesEmbedBuilder extends EmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
@ -501,6 +501,7 @@ class NotesEmbedBuilder implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
bool inline,
) {
final notes = NotesBlockEmbed(node.value.data).document;

@ -27,7 +27,7 @@ class UniversalUI {
var ui = UniversalUI();
class ImageEmbedBuilderWeb implements EmbedBuilder {
class ImageEmbedBuilderWeb extends EmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@ -37,6 +37,7 @@ class ImageEmbedBuilderWeb implements EmbedBuilder {
QuillController controller,
Embed node,
bool readOnly,
bool inline,
) {
final imageUrl = node.value.data;
if (isImageBase64(imageUrl)) {
@ -68,13 +69,18 @@ class ImageEmbedBuilderWeb implements EmbedBuilder {
}
}
class VideoEmbedBuilderWeb implements EmbedBuilder {
class VideoEmbedBuilderWeb extends EmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
Widget build(BuildContext context, QuillController controller, Embed node,
bool readOnly) {
Widget build(
BuildContext context,
QuillController controller,
Embed node,
bool readOnly,
bool inline,
) {
var videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
final youtubeID = YoutubePlayer.convertUrlToId(videoUrl);

@ -14,7 +14,7 @@ import 'widgets/image_resizer.dart';
import 'widgets/video_app.dart';
import 'widgets/youtube_video_app.dart';
class ImageEmbedBuilder implements EmbedBuilder {
class ImageEmbedBuilder extends EmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@ -24,6 +24,7 @@ class ImageEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
bool inline,
) {
assert(!kIsWeb, 'Please provide image EmbedBuilder for Web');
@ -144,7 +145,7 @@ class ImageEmbedBuilder implements EmbedBuilder {
}
}
class VideoEmbedBuilder implements EmbedBuilder {
class VideoEmbedBuilder extends EmbedBuilder {
VideoEmbedBuilder({this.onVideoInit});
final void Function(GlobalKey videoContainerKey)? onVideoInit;
@ -158,6 +159,7 @@ class VideoEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
bool inline,
) {
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web');
@ -175,7 +177,7 @@ class VideoEmbedBuilder implements EmbedBuilder {
}
}
class FormulaEmbedBuilder implements EmbedBuilder {
class FormulaEmbedBuilder extends EmbedBuilder {
@override
String get key => BlockEmbed.formulaType;
@ -185,6 +187,7 @@ class FormulaEmbedBuilder implements EmbedBuilder {
QuillController controller,
base.Embed node,
bool readOnly,
bool inline,
) {
assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web');

@ -8,14 +8,10 @@ import '../models/documents/nodes/leaf.dart';
import '../utils/platform.dart';
import 'controller.dart';
import 'editor.dart';
import 'embeds.dart';
import 'text_selection.dart';
typedef EmbedsBuilder = Widget Function(
BuildContext context,
QuillController controller,
Embed node,
bool readOnly,
);
typedef EmbedsBuilder = EmbedBuilder Function(Embed node);
typedef CustomStyleBuilder = TextStyle Function(Attribute attribute);

@ -363,7 +363,7 @@ class QuillEditor extends StatefulWidget {
onSingleLongTapEnd;
final Iterable<EmbedBuilder>? embedBuilders;
final EmbedsBuilder? unknownEmbedBuilder;
final EmbedBuilder? unknownEmbedBuilder;
final CustomStyleBuilder? customStyleBuilder;
/// The locale to use for the editor toolbar, defaults to system locale
@ -492,19 +492,7 @@ class QuillEditorState extends State<QuillEditor>
keyboardAppearance: widget.keyboardAppearance,
enableInteractiveSelection: widget.enableInteractiveSelection,
scrollPhysics: widget.scrollPhysics,
embedBuilder: (
context,
controller,
node,
readOnly,
) =>
_buildCustomBlockEmbed(
node,
context,
controller,
readOnly,
widget.unknownEmbedBuilder,
),
embedBuilder: _getEmbedBuilder,
linkActionPickerDelegate: widget.linkActionPickerDelegate,
customStyleBuilder: widget.customStyleBuilder,
floatingCursorDisabled: widget.floatingCursorDisabled,
@ -541,31 +529,19 @@ class QuillEditorState extends State<QuillEditor>
return editor;
}
Widget _buildCustomBlockEmbed(
Embed node,
BuildContext context,
QuillController controller,
bool readOnly,
EmbedsBuilder? unknownEmbedBuilder,
) {
EmbedBuilder _getEmbedBuilder(Embed node) {
final builders = widget.embedBuilders;
var _node = node;
// Creates correct node for custom embed
if (node.value.type == BlockEmbed.customType) {
_node = Embed(CustomBlockEmbed.fromJsonString(node.value.data));
}
if (builders != null) {
for (final builder in builders) {
if (builder.key == _node.value.type) {
return builder.build(context, controller, _node, readOnly);
if (builder.key == node.value.type) {
return builder;
}
}
}
if (unknownEmbedBuilder != null) {
return unknownEmbedBuilder(context, controller, _node, readOnly);
if (widget.unknownEmbedBuilder != null) {
return widget.unknownEmbedBuilder!;
}
throw UnimplementedError(

@ -7,12 +7,18 @@ import 'controller.dart';
abstract class EmbedBuilder {
String get key;
bool get expanded => true;
WidgetSpan buildWidgetSpan(Widget widget) {
return WidgetSpan(child: widget);
}
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
bool inline,
);
}

@ -10,6 +10,7 @@ import 'package:url_launcher/url_launcher.dart';
import '../models/documents/attribute.dart';
import '../models/documents/nodes/container.dart' as container_node;
import '../models/documents/nodes/embeddable.dart';
import '../models/documents/nodes/leaf.dart';
import '../models/documents/nodes/leaf.dart' as leaf;
import '../models/documents/nodes/line.dart';
@ -132,17 +133,28 @@ class _TextLineState extends State<TextLine> {
@override
Widget build(BuildContext context) {
assert(debugCheckHasMediaQuery(context));
if (widget.line.hasEmbed && widget.line.childCount == 1) {
// For video, it is always single child
final embed = widget.line.children.single as Embed;
return EmbedProxy(
widget.embedBuilder(
context,
widget.controller,
embed,
widget.readOnly,
),
);
// Single child embeds can be expanded
var embed = widget.line.children.single as Embed;
// Creates correct node for custom embed
if (embed.value.type == BlockEmbed.customType) {
embed = Embed(CustomBlockEmbed.fromJsonString(embed.value.data));
}
final embedBuilder = widget.embedBuilder(embed);
if (embedBuilder.expanded) {
// Creates correct node for custom embed
return EmbedProxy(
embedBuilder.build(
context,
widget.controller,
embed,
widget.readOnly,
false,
),
);
}
}
final textSpan = _getTextSpanForWholeLine(context);
final strutStyle = StrutStyle.fromTextStyle(textSpan.style!);
@ -173,24 +185,28 @@ class _TextLineState extends State<TextLine> {
// The line could contain more than one Embed & more than one Text
final textSpanChildren = <InlineSpan>[];
var textNodes = LinkedList<Node>();
for (final child in widget.line.children) {
for (var child in widget.line.children) {
if (child is Embed) {
if (textNodes.isNotEmpty) {
textSpanChildren
.add(_buildTextSpan(widget.styles, textNodes, lineStyle));
textNodes = LinkedList<Node>();
}
// Here it should be image
final embed = WidgetSpan(
child: EmbedProxy(
widget.embedBuilder(
context,
widget.controller,
child,
widget.readOnly,
),
// Creates correct node for custom embed
if (child.value.type == BlockEmbed.customType) {
child = Embed(CustomBlockEmbed.fromJsonString(child.value.data));
}
final embedBuilder = widget.embedBuilder(child);
final embedWidget = EmbedProxy(
embedBuilder.build(
context,
widget.controller,
child,
widget.readOnly,
true,
),
);
final embed = embedBuilder.buildWidgetSpan(embedWidget);
textSpanChildren.add(embed);
continue;
}

Loading…
Cancel
Save