Consolidated embed builders

pull/933/head
Jonathan Salmon 3 years ago
parent 616fae851b
commit 606806ff6f
  1. 34
      example/lib/pages/home_page.dart
  2. 3
      example/lib/pages/read_only_page.dart
  3. 38
      example/lib/universal_ui/universal_ui.dart
  4. 73
      lib/src/embeds/default_embed_builder.dart
  5. 48
      lib/src/widgets/editor.dart
  6. 2
      lib/src/widgets/raw_editor.dart

@ -119,7 +119,10 @@ class _HomePageState extends State<HomePage> {
null),
sizeSmall: const TextStyle(fontSize: 9),
),
customElementsEmbedBuilder: customElementsEmbedBuilder,
embedBuilders: [
...defaultEmbedBuilders,
NotesEmbedBuilder(addEditNote: _addEditNote)
],
);
if (kIsWeb) {
quillEditor = QuillEditor(
@ -145,7 +148,7 @@ class _HomePageState extends State<HomePage> {
null),
sizeSmall: const TextStyle(fontSize: 9),
),
embedBuilder: defaultEmbedBuilderWeb);
embedBuilders: defaultEmbedBuildersWeb);
}
var toolbar = QuillToolbar.basic(
controller: _controller!,
@ -386,17 +389,25 @@ class _HomePageState extends State<HomePage> {
controller.replaceText(index, length, block, null);
}
}
}
class NotesEmbedBuilder implements IEmbedBuilder {
NotesEmbedBuilder({required this.addEditNote});
Future<void> Function(BuildContext context, {Document? document}) addEditNote;
Widget customElementsEmbedBuilder(
@override
String get key => 'notes';
@override
Widget build(
BuildContext context,
QuillController controller,
CustomBlockEmbed block,
Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
) {
switch (block.type) {
case 'notes':
final notes = NotesBlockEmbed(block.data).document;
void Function(GlobalKey<State<StatefulWidget>> videoContainerKey)?
onVideoInit) {
final notes = NotesBlockEmbed(node.value.data).document;
return Material(
color: Colors.transparent,
@ -407,16 +418,13 @@ class _HomePageState extends State<HomePage> {
overflow: TextOverflow.ellipsis,
),
leading: const Icon(Icons.notes),
onTap: () => _addEditNote(context, document: notes),
onTap: () => addEditNote(context, document: notes),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
side: const BorderSide(color: Colors.grey),
),
),
);
default:
return const SizedBox();
}
}
}

@ -38,6 +38,7 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuilders,
);
if (kIsWeb) {
quillEditor = QuillEditor(
@ -49,7 +50,7 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilder: defaultEmbedBuilderWeb);
embedBuilders: defaultEmbedBuildersWeb);
}
return Padding(
padding: const EdgeInsets.all(8),

@ -26,15 +26,13 @@ class UniversalUI {
var ui = UniversalUI();
Widget defaultEmbedBuilderWeb(
BuildContext context,
QuillController controller,
Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
) {
switch (node.value.type) {
case BlockEmbed.imageType:
class ImageEmbedBuilderWeb implements IEmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@override
Widget build(BuildContext context, QuillController controller, Embed node,
bool readOnly, void Function(GlobalKey videoContainerKey)? onVideoInit) {
final imageUrl = node.value.data;
if (isImageBase64(imageUrl)) {
// TODO: handle imageUrl of base64
@ -58,7 +56,16 @@ Widget defaultEmbedBuilderWeb(
),
),
);
case BlockEmbed.videoType:
}
}
class VideoEmbedBuilderWeb implements IEmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
Widget build(BuildContext context, QuillController controller, Embed node,
bool readOnly, void Function(GlobalKey videoContainerKey)? onVideoInit) {
var videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
final youtubeID = YoutubePlayer.convertUrlToId(videoUrl);
@ -81,11 +88,10 @@ Widget defaultEmbedBuilderWeb(
viewType: videoUrl,
),
);
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.',
);
}
}
List<IEmbedBuilder> get defaultEmbedBuildersWeb => [
ImageEmbedBuilderWeb(),
VideoEmbedBuilderWeb(),
];

@ -36,21 +36,35 @@ typedef WebVideoPickImpl = Future<String?> Function(
typedef MediaPickSettingSelector = Future<MediaPickSetting?> Function(
BuildContext context);
abstract class IEmbedBuilder {
String get key;
Widget defaultEmbedBuilder(
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
);
}
class ImageEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.imageType;
@override
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
) {
assert(!kIsWeb, 'Please provide EmbedBuilder for Web');
assert(!kIsWeb, 'Please provide image EmbedBuilder for Web');
Tuple2<double?, double?>? _widthHeight;
switch (node.value.type) {
case BlockEmbed.imageType:
final imageUrl = standardizeImageUrl(node.value.data);
var image;
final imageUrl = standardizeImageUrl(node.value.data);
Tuple2<double?, double?>? _widthHeight;
final style = node.style.attributes['style'];
if (isMobile() && style != null) {
final _attrs = parseKeyValuePairs(style.value.toString(), {
@ -162,7 +176,22 @@ Widget defaultEmbedBuilder(
// We provide option menu for mobile platform excluding base64 image
return _menuOptionsForReadonlyImage(context, imageUrl, image);
case BlockEmbed.videoType:
}
}
class VideoEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.videoType;
@override
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web');
final videoUrl = node.value.data;
if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) {
return YoutubeVideoApp(
@ -174,9 +203,23 @@ Widget defaultEmbedBuilder(
readOnly: readOnly,
onVideoInit: onVideoInit,
);
case BlockEmbed.formulaType:
final mathController = MathFieldEditingController();
}
}
class FormulaEmbedBuilder implements IEmbedBuilder {
@override
String get key => BlockEmbed.formulaType;
@override
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web');
final mathController = MathFieldEditingController();
return Focus(
onFocusChange: (hasFocus) {
if (hasFocus) {
@ -192,15 +235,15 @@ Widget defaultEmbedBuilder(
onSubmitted: (value) {},
),
);
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.',
);
}
}
List<IEmbedBuilder> get defaultEmbedBuilders => [
ImageEmbedBuilder(),
VideoEmbedBuilder(),
FormulaEmbedBuilder(),
];
Widget _menuOptionsForReadonlyImage(
BuildContext context, String imageUrl, Widget image) {
return GestureDetector(

@ -9,6 +9,8 @@ import 'package:flutter/services.dart';
import 'package:i18n_extension/i18n_widget.dart';
import 'package:tuple/tuple.dart';
import '../../flutter_quill.dart';
import '../embeds/default_embed_builder.dart';
import '../models/documents/document.dart';
import '../models/documents/nodes/container.dart' as container_node;
import '../models/documents/nodes/embeddable.dart';
@ -19,7 +21,6 @@ import 'controller.dart';
import 'cursor.dart';
import 'default_styles.dart';
import 'delegate.dart';
import '../embeds/default_embed_builder.dart';
import 'float_cursor.dart';
import 'link.dart';
import 'raw_editor.dart';
@ -168,8 +169,7 @@ class QuillEditor extends StatefulWidget {
this.onSingleLongTapStart,
this.onSingleLongTapMoveUpdate,
this.onSingleLongTapEnd,
this.embedBuilder = defaultEmbedBuilder,
this.customElementsEmbedBuilder,
this.embedBuilders,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.locale,
@ -182,6 +182,7 @@ class QuillEditor extends StatefulWidget {
required QuillController controller,
required bool readOnly,
Brightness? keyboardAppearance,
Iterable<IEmbedBuilder>? embedBuilders,
/// The locale to use for the editor toolbar, defaults to system locale
/// More at https://github.com/singerdmx/flutter-quill#translation
@ -198,6 +199,7 @@ class QuillEditor extends StatefulWidget {
padding: EdgeInsets.zero,
keyboardAppearance: keyboardAppearance ?? Brightness.light,
locale: locale,
embedBuilders: embedBuilders,
);
}
@ -346,8 +348,7 @@ class QuillEditor extends StatefulWidget {
LongPressEndDetails details, TextPosition Function(Offset offset))?
onSingleLongTapEnd;
final EmbedBuilder embedBuilder;
final CustomEmbedBuilder? customElementsEmbedBuilder;
final Iterable<IEmbedBuilder>? embedBuilders;
final CustomStyleBuilder? customStyleBuilder;
/// The locale to use for the editor toolbar, defaults to system locale
@ -473,23 +474,28 @@ class QuillEditorState extends State<QuillEditor>
readOnly,
onVideoInit,
) {
final customElementsEmbedBuilder = widget.customElementsEmbedBuilder;
final isCustomType = node.value.type == BlockEmbed.customType;
if (customElementsEmbedBuilder != null && isCustomType) {
return customElementsEmbedBuilder(
context,
controller,
CustomBlockEmbed.fromJsonString(node.value.data),
readOnly,
onVideoInit,
);
final builders = widget.embedBuilders;
if (builders != null) {
var _node = node;
// Creates correct node for custom embed
if (node.value.type == BlockEmbed.customType) {
_node = Embed(CustomBlockEmbed.fromJsonString(node.value.data));
}
return widget.embedBuilder(
context,
controller,
node,
readOnly,
onVideoInit,
for (final builder in builders) {
if (builder.key == _node.value.type) {
return builder.build(
context, controller, _node, readOnly, onVideoInit);
}
}
}
throw UnimplementedError(
'Embeddable type "${node.value.type}" is not supported by supplied '
'embed builders. You must pass your own builder function to '
'embedBuilders property of QuillEditor or QuillField widgets.',
);
},
linkActionPickerDelegate: widget.linkActionPickerDelegate,

@ -46,6 +46,7 @@ class RawEditor extends StatefulWidget {
required this.cursorStyle,
required this.selectionColor,
required this.selectionCtrls,
required this.embedBuilder,
Key? key,
this.scrollable = true,
this.padding = EdgeInsets.zero,
@ -70,7 +71,6 @@ class RawEditor extends StatefulWidget {
this.keyboardAppearance = Brightness.light,
this.enableInteractiveSelection = true,
this.scrollPhysics,
this.embedBuilder = defaultEmbedBuilder,
this.linkActionPickerDelegate = defaultLinkActionPickerDelegate,
this.customStyleBuilder,
this.floatingCursorDisabled = false})

Loading…
Cancel
Save