Skip to content

Commit 6c9e2eb

Browse files
committed
New Embed API
1 parent 7ec9ee2 commit 6c9e2eb

9 files changed

Lines changed: 298 additions & 232 deletions

File tree

packages/fleather/example/assets/welcome.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
},
2020
{
2121
"insert": {
22-
"_type": "image",
22+
"_type": "block_image",
2323
"_inline": false,
2424
"source": "images/breeze.jpg",
2525
"source_type": "assets"
@@ -166,7 +166,7 @@
166166
},
167167
{
168168
"insert": {
169-
"_type": "image",
169+
"_type": "span_image",
170170
"_inline": true,
171171
"source": "images/breeze.jpg",
172172
"orientation": "landscape",
@@ -177,7 +177,7 @@
177177
},
178178
{
179179
"insert": {
180-
"_type": "image",
180+
"_type": "span_image",
181181
"_inline": true,
182182
"source": "images/cody-manning-82xt4MtbJqg-unsplash.jpg",
183183
"source_type": "assets",

packages/fleather/example/ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,9 @@ EXTERNAL SOURCES:
2525

2626
SPEC CHECKSUMS:
2727
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
28-
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
29-
integration_test: 4a889634ef21a45d28d50d622cf412dc6d9f586e
30-
url_launcher_ios: 694010445543906933d732453a59da0a173ae33d
28+
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
29+
integration_test: 252f60fa39af5e17c3aa9899d35d908a0721b573
30+
url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe
3131

3232
PODFILE CHECKSUM: 0dbd5a87e0ace00c9610d2037ac22083a01f861d
3333

packages/fleather/example/lib/main.dart

Lines changed: 70 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -130,7 +130,9 @@ class _HomePageState extends State<HomePage> {
130130
),
131131
onLaunchUrl: _launchUrl,
132132
maxContentWidth: 800,
133-
embedBuilder: _embedBuilder,
133+
embedRegistry: EmbedRegistry.fallbackWithConfigurations(
134+
[IconEmbed(), ImageBlockEmbed(), ImageSpanEmbed()],
135+
),
134136
spellCheckConfiguration: SpellCheckConfiguration(
135137
spellCheckService: DefaultSpellCheckService(),
136138
misspelledSelectionColor: Colors.red,
@@ -143,56 +145,6 @@ class _HomePageState extends State<HomePage> {
143145
);
144146
}
145147

146-
Widget _embedBuilder(BuildContext context, EmbedNode node) {
147-
if (node.value.type == 'icon') {
148-
final data = node.value.data;
149-
// Icons.rocket_launch_outlined
150-
return Icon(
151-
IconData(int.parse(data['codePoint']), fontFamily: data['fontFamily']),
152-
color: Color(int.parse(data['color'])),
153-
size: 18,
154-
);
155-
}
156-
157-
if (node.value.type == 'image') {
158-
final sourceType = node.value.data['source_type'];
159-
ImageProvider? image;
160-
if (sourceType == 'assets') {
161-
image = AssetImage(node.value.data['source']);
162-
} else if (sourceType == 'file') {
163-
image = FileImage(File(node.value.data['source']));
164-
} else if (sourceType == 'url') {
165-
image = NetworkImage(node.value.data['source']);
166-
} else if (sourceType == 'data') {
167-
// source: 'data:image/jpeg;base64, LzlqLzRBQ... <!-- Base64 data -->'
168-
RegExp regex = RegExp(
169-
r'^data:image\/(png|jpe?g|gif|bmp|webp);base64,',
170-
caseSensitive: false,
171-
);
172-
if (regex.hasMatch(node.value.data['source'])) {
173-
String base64Image =
174-
node.value.data['source'].replaceFirst(regex, '');
175-
image = MemoryImage(base64Decode(base64Image));
176-
}
177-
}
178-
if (image != null) {
179-
return Padding(
180-
// Caret takes 2 pixels, hence not symmetric padding values.
181-
padding: const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2),
182-
child: Container(
183-
width: (node.value.data['width'] as num?)?.toDouble() ?? 300,
184-
height: (node.value.data['height'] as num?)?.toDouble() ?? 300,
185-
decoration: BoxDecoration(
186-
image: DecorationImage(image: image, fit: BoxFit.cover),
187-
),
188-
),
189-
);
190-
}
191-
}
192-
193-
return defaultFleatherEmbedBuilder(context, node);
194-
}
195-
196148
void _launchUrl(String? url) async {
197149
if (url == null) return;
198150
final uri = Uri.parse(url);
@@ -203,6 +155,73 @@ class _HomePageState extends State<HomePage> {
203155
}
204156
}
205157

158+
class IconEmbed extends SpanEmbedConfiguration {
159+
const IconEmbed()
160+
: super(type: 'icon', alignment: PlaceholderAlignment.middle);
161+
162+
@override
163+
Widget build(BuildContext context, Map<String, dynamic> data) {
164+
// Icons.rocket_launch_outlined
165+
return Icon(
166+
IconData(int.parse(data['codePoint']), fontFamily: data['fontFamily']),
167+
color: Color(int.parse(data['color'])),
168+
size: 18,
169+
);
170+
}
171+
}
172+
173+
Widget _imageBuilder(BuildContext context, Map<String, dynamic> data) {
174+
final sourceType = data['source_type'];
175+
ImageProvider? image;
176+
if (sourceType == 'assets') {
177+
image = AssetImage(data['source']);
178+
} else if (sourceType == 'file') {
179+
image = FileImage(File(data['source']));
180+
} else if (sourceType == 'url') {
181+
image = NetworkImage(data['source']);
182+
} else if (sourceType == 'data') {
183+
// source: 'data:image/jpeg;base64, LzlqLzRBQ... <!-- Base64 data -->'
184+
RegExp regex = RegExp(
185+
r'^data:image\/(png|jpe?g|gif|bmp|webp);base64,',
186+
caseSensitive: false,
187+
);
188+
if (regex.hasMatch(data['source'])) {
189+
String base64Image = data['source'].replaceFirst(regex, '');
190+
image = MemoryImage(base64Decode(base64Image));
191+
}
192+
}
193+
if (image == null) {
194+
throw ArgumentError('Could create an ImageProvider from the provided data');
195+
}
196+
return Padding(
197+
// Caret takes 2 pixels, hence not symmetric padding values.
198+
padding: const EdgeInsets.only(left: 4, right: 2, top: 2, bottom: 2),
199+
child: Container(
200+
width: (data['width'] as num?)?.toDouble() ?? 300,
201+
height: (data['height'] as num?)?.toDouble() ?? 300,
202+
decoration: BoxDecoration(
203+
image: DecorationImage(image: image, fit: BoxFit.cover),
204+
),
205+
),
206+
);
207+
}
208+
209+
class ImageSpanEmbed extends SpanEmbedConfiguration {
210+
ImageSpanEmbed() : super(type: 'span_image');
211+
212+
@override
213+
Widget build(BuildContext context, Map<String, dynamic> data) =>
214+
_imageBuilder(context, data);
215+
}
216+
217+
class ImageBlockEmbed extends BlockEmbedConfiguration {
218+
ImageBlockEmbed() : super(type: 'block_image');
219+
220+
@override
221+
Widget build(BuildContext context, Map<String, dynamic> data) =>
222+
_imageBuilder(context, data);
223+
}
224+
206225
/// This is an example insert rule that will insert a new line before and
207226
/// after inline image embed.
208227
class ForceNewlineForInsertsAroundInlineImageRule extends InsertRule {

packages/fleather/lib/fleather.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export 'src/widgets/controller.dart';
1313
export 'src/widgets/cursor.dart';
1414
export 'src/widgets/editor.dart';
1515
export 'src/widgets/editor_toolbar.dart';
16+
export 'src/widgets/embed_registry.dart';
1617
export 'src/widgets/field.dart';
1718
export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
1819
export 'src/widgets/text_line.dart';

packages/fleather/lib/src/widgets/editable_text_block.dart

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import 'package:collection/collection.dart';
2+
import 'package:fleather/src/widgets/embed_registry.dart';
23
import 'package:flutter/material.dart';
34
import 'package:parchment/parchment.dart';
45

@@ -8,7 +9,6 @@ import 'checkbox.dart';
89
import 'controller.dart';
910
import 'cursor.dart';
1011
import 'editable_text_line.dart';
11-
import 'editor.dart';
1212
import 'link.dart';
1313
import 'text_line.dart';
1414
import 'theme.dart';
@@ -24,8 +24,7 @@ class EditableTextBlock extends StatelessWidget {
2424
final Color selectionColor;
2525
final bool enableInteractiveSelection;
2626
final bool hasFocus;
27-
final FleatherEmbedBuilder embedBuilder;
28-
final Map<String, FleatherSpanEmbedConfiguration> spanEmbedConfigurations;
27+
final EmbedRegistry embedRegistry;
2928
final LinkActionPicker linkActionPicker;
3029
final ValueChanged<String?>? onLaunchUrl;
3130
final EdgeInsets? contentPadding;
@@ -42,8 +41,7 @@ class EditableTextBlock extends StatelessWidget {
4241
required this.selectionColor,
4342
required this.enableInteractiveSelection,
4443
required this.hasFocus,
45-
required this.embedBuilder,
46-
required this.spanEmbedConfigurations,
44+
required this.embedRegistry,
4745
required this.linkActionPicker,
4846
this.onLaunchUrl,
4947
this.contentPadding,
@@ -85,8 +83,7 @@ class EditableTextBlock extends StatelessWidget {
8583
node: line,
8684
readOnly: readOnly,
8785
controller: controller,
88-
embedBuilder: embedBuilder,
89-
spanEmbedConfigurations: spanEmbedConfigurations,
86+
embedRegistry: embedRegistry,
9087
linkActionPicker: linkActionPicker,
9188
onLaunchUrl: onLaunchUrl,
9289
textWidthBasis: textWidthBasis,

0 commit comments

Comments
 (0)