Moved embed code to separate package

pull/933/head
Jonathan Salmon 3 years ago
parent 97375b5262
commit e30786cd95
  1. 9
      example/lib/pages/home_page.dart
  2. 3
      example/lib/pages/read_only_page.dart
  3. 1
      example/lib/universal_ui/universal_ui.dart
  4. 5
      example/lib/widgets/demo_scaffold.dart
  5. 6
      example/pubspec.yaml
  6. 10
      flutter_quill_extensions/.metadata
  7. 3
      flutter_quill_extensions/CHANGELOG.md
  8. 1
      flutter_quill_extensions/LICENSE
  9. 22
      flutter_quill_extensions/README.md
  10. 37
      flutter_quill_extensions/analysis_options.yaml
  11. 53
      flutter_quill_extensions/lib/embeds/builders.dart
  12. 0
      flutter_quill_extensions/lib/embeds/embed_types.dart
  13. 19
      flutter_quill_extensions/lib/embeds/toolbar.dart
  14. 8
      flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
  15. 7
      flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart
  16. 7
      flutter_quill_extensions/lib/embeds/toolbar/image_button.dart
  17. 10
      flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
  18. 7
      flutter_quill_extensions/lib/embeds/toolbar/video_button.dart
  19. 5
      flutter_quill_extensions/lib/embeds/utils.dart
  20. 10
      flutter_quill_extensions/lib/embeds/widgets/image.dart
  21. 2
      flutter_quill_extensions/lib/embeds/widgets/image_resizer.dart
  22. 3
      flutter_quill_extensions/lib/embeds/widgets/video_app.dart
  23. 3
      flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart
  24. 18
      flutter_quill_extensions/lib/flutter_quill_extensions.dart
  25. 36
      flutter_quill_extensions/pubspec.yaml
  26. 6
      lib/extensions.dart
  27. 5
      lib/flutter_quill.dart
  28. 7
      lib/src/widgets/editor.dart
  29. 25
      lib/src/widgets/embeds.dart
  30. 1
      lib/src/widgets/raw_editor.dart
  31. 2
      lib/src/widgets/toolbar.dart
  32. 3
      lib/translations.dart
  33. 7
      pubspec.yaml

@ -8,6 +8,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';
import 'package:tuple/tuple.dart';
@ -120,7 +121,7 @@ class _HomePageState extends State<HomePage> {
sizeSmall: const TextStyle(fontSize: 9),
),
embedBuilders: [
...defaultEmbedBuilders,
...FlutterQuillEmbeds.builders,
NotesEmbedBuilder(addEditNote: _addEditNote)
],
);
@ -152,7 +153,7 @@ class _HomePageState extends State<HomePage> {
}
var toolbar = QuillToolbar.basic(
controller: _controller!,
embedToolbar: EmbedToolbar(
embedToolbar: QuillEmbedToolbar(
// provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url.
// same goes for videos.
@ -168,7 +169,7 @@ class _HomePageState extends State<HomePage> {
if (kIsWeb) {
toolbar = QuillToolbar.basic(
controller: _controller!,
embedToolbar: EmbedToolbar(
embedToolbar: QuillEmbedToolbar(
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
),
@ -178,7 +179,7 @@ class _HomePageState extends State<HomePage> {
if (_isDesktop()) {
toolbar = QuillToolbar.basic(
controller: _controller!,
embedToolbar: EmbedToolbar(
embedToolbar: QuillEmbedToolbar(
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
),

@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import '../universal_ui/universal_ui.dart';
import '../widgets/demo_scaffold.dart';
@ -38,7 +39,7 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
readOnly: !_edit,
expands: false,
padding: EdgeInsets.zero,
embedBuilders: defaultEmbedBuilders,
embedBuilders: FlutterQuillEmbeds.builders,
);
if (kIsWeb) {
quillEditor = QuillEditor(

@ -3,6 +3,7 @@ library universal_ui;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:universal_html/html.dart' as html;
import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart';

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path_provider/path_provider.dart';
typedef DemoContentBuilder = Widget Function(
@ -91,13 +92,13 @@ class _DemoScaffoldState extends State<DemoScaffold> {
final actions = widget.actions ?? <Widget>[];
var toolbar = QuillToolbar.basic(
controller: _controller!,
embedToolbar: EmbedToolbar(),
embedToolbar: QuillEmbedToolbar(),
);
if (_isDesktop()) {
toolbar = QuillToolbar.basic(
controller: _controller!,
embedToolbar:
EmbedToolbar(filePickImpl: openFileSystemPickerForDesktop),
QuillEmbedToolbar(filePickImpl: openFileSystemPickerForDesktop),
);
}
return Scaffold(

@ -34,6 +34,12 @@ dependencies:
file_picker: ^4.6.1
flutter_quill:
path: ../
flutter_quill_extensions:
path: ../flutter_quill_extensions
dependency_overrides:
flutter_quill:
path: ../
dev_dependencies:
flutter_test:

@ -0,0 +1,10 @@
# This file tracks properties of this Flutter project.
# Used by Flutter tool to assess capabilities and perform upgrades etc.
#
# This file should be version controlled and should not be manually edited.
version:
revision: f1875d570e39de09040c8f79aa13cc56baab8db1
channel: stable
project_type: package

@ -0,0 +1,3 @@
## 0.0.1
* TODO: Describe initial release.

@ -0,0 +1 @@
TODO: Add your license here.

@ -0,0 +1,22 @@
# Flutter Quill Extensions
Helpers to support embed widgets in flutter_quill.
## Usage
Set the `embedBuilders` and `embedToolbar` params in `QuillEditor` and `QuillToolbar` with the
values provided by this repository.
```
QuillEditor.basic(
controller: controller,
embedBuilders: FlutterQuillEmbeds.builders,
);
```
```
QuillToolbar.basic(
controller: controller,
embedToolbar: QuillEmbedToolbar(),
);
```

@ -0,0 +1,37 @@
include: package:pedantic/analysis_options.yaml
analyzer:
errors:
undefined_prefixed_name: ignore
unsafe_html: ignore
linter:
rules:
- always_declare_return_types
- always_put_required_named_parameters_first
- annotate_overrides
- avoid_empty_else
- avoid_escaping_inner_quotes
- avoid_print
- avoid_redundant_argument_values
- avoid_types_on_closure_parameters
- avoid_void_async
- cascade_invocations
- directives_ordering
- lines_longer_than_80_chars
- omit_local_variable_types
- prefer_const_constructors
- prefer_const_constructors_in_immutables
- prefer_const_declarations
- prefer_final_fields
- prefer_final_in_for_each
- prefer_final_locals
- prefer_initializing_formals
- prefer_int_literals
- prefer_interpolation_to_compose_strings
- prefer_relative_imports
- prefer_single_quotes
- sort_constructors_first
- sort_unnamed_constructors_first
- unnecessary_lambdas
- unnecessary_parenthesis
- unnecessary_string_interpolations

@ -2,39 +2,20 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:gallery_saver/gallery_saver.dart';
import 'package:math_keyboard/math_keyboard.dart';
import 'package:tuple/tuple.dart';
import '../models/documents/attribute.dart';
import '../models/documents/nodes/embeddable.dart';
import '../models/documents/nodes/leaf.dart' as leaf;
import '../translations/toolbar.i18n.dart';
import '../utils/embeds.dart';
import '../utils/platform.dart';
import '../utils/string.dart';
import '../widgets/controller.dart';
import 'package:flutter_quill/translations.dart';
import 'package:flutter_quill/extensions.dart' as base;
import 'utils.dart';
import 'widgets/image.dart';
import 'widgets/image_resizer.dart';
import 'widgets/video_app.dart';
import 'widgets/youtube_video_app.dart';
export 'toolbar/image_button.dart';
export 'toolbar/image_video_utils.dart';
export 'toolbar/video_button.dart';
abstract class IEmbedBuilder {
String get key;
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;
@ -43,7 +24,7 @@ class ImageEmbedBuilder implements IEmbedBuilder {
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
base.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
) {
@ -53,8 +34,8 @@ class ImageEmbedBuilder implements IEmbedBuilder {
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(), {
if (base.isMobile() && style != null) {
final _attrs = base.parseKeyValuePairs(style.value.toString(), {
Attribute.mobileWidth,
Attribute.mobileHeight,
Attribute.mobileMargin,
@ -71,7 +52,7 @@ class ImageEmbedBuilder implements IEmbedBuilder {
final m = _attrs[Attribute.mobileMargin] == null
? 0.0
: double.parse(_attrs[Attribute.mobileMargin]!);
final a = getAlignment(_attrs[Attribute.mobileAlignment]);
final a = base.getAlignment(_attrs[Attribute.mobileAlignment]);
image = Padding(
padding: EdgeInsets.all(m),
child: imageByUrl(imageUrl, width: w, height: h, alignment: a));
@ -83,7 +64,7 @@ class ImageEmbedBuilder implements IEmbedBuilder {
_widthHeight = Tuple2((image as Image).width, image.height);
}
if (!readOnly && isMobile()) {
if (!readOnly && base.isMobile()) {
return GestureDetector(
onTap: () {
showDialog(
@ -103,7 +84,7 @@ class ImageEmbedBuilder implements IEmbedBuilder {
onImageResize: (w, h) {
final res = getEmbedNode(
controller, controller.selection.start);
final attr = replaceStyleString(
final attr = base.replaceStyleString(
getImageStyleString(controller), w, h);
controller
..skipRequestKeyboard = true
@ -157,7 +138,7 @@ class ImageEmbedBuilder implements IEmbedBuilder {
child: image);
}
if (!readOnly || !isMobile() || isImageBase64(imageUrl)) {
if (!readOnly || !base.isMobile() || isImageBase64(imageUrl)) {
return image;
}
@ -174,7 +155,7 @@ class VideoEmbedBuilder implements IEmbedBuilder {
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
base.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide video EmbedBuilder for Web');
@ -201,7 +182,7 @@ class FormulaEmbedBuilder implements IEmbedBuilder {
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
base.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit) {
assert(!kIsWeb, 'Please provide formula EmbedBuilder for Web');
@ -225,12 +206,6 @@ class FormulaEmbedBuilder implements IEmbedBuilder {
}
}
List<IEmbedBuilder> get defaultEmbedBuilders => [
ImageEmbedBuilder(),
VideoEmbedBuilder(),
FormulaEmbedBuilder(),
];
Widget _menuOptionsForReadonlyImage(
BuildContext context, String imageUrl, Widget image) {
return GestureDetector(

@ -1,23 +1,20 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import '../models/themes/quill_dialog_theme.dart';
import '../models/themes/quill_icon_theme.dart';
import '../widgets/controller.dart';
import 'embed_types.dart';
import 'toolbar/camera_button.dart';
import 'toolbar/formula_button.dart';
import 'toolbar/image_button.dart';
import 'toolbar/video_button.dart';
abstract class IEmbedToolbar {
Iterable<Widget> build(QuillController controller, double toolbarIconSize,
QuillIconTheme? iconTheme, QuillDialogTheme? dialogTheme);
bool get notEmpty;
}
export 'toolbar/image_button.dart';
export 'toolbar/image_video_utils.dart';
export 'toolbar/video_button.dart';
export 'toolbar/formula_button.dart';
export 'toolbar/camera_button.dart';
class EmbedToolbar implements IEmbedToolbar {
EmbedToolbar({
class QuillEmbedToolbar implements IEmbedToolbar {
QuillEmbedToolbar({
this.showImageButton = true,
this.showVideoButton = true,
this.showCameraButton = true,

@ -1,11 +1,9 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:image_picker/image_picker.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../widgets/controller.dart';
import '../../widgets/toolbar.dart';
import '../default_embed_builder.dart';
import 'package:flutter_quill/translations.dart';
import '../embed_types.dart';
import 'image_video_utils.dart';

@ -1,11 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../widgets/controller.dart';
import '../../widgets/toolbar.dart';
import '../default_embed_builder.dart';
import '../embed_types.dart';
class FormulaButton extends StatelessWidget {

@ -1,12 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:image_picker/image_picker.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../widgets/controller.dart';
import '../../widgets/toolbar.dart';
import '../default_embed_builder.dart';
import '../embed_types.dart';
import 'image_video_utils.dart';

@ -2,14 +2,12 @@ import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text;
import 'package:image_picker/image_picker.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/rules/insert.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../translations/toolbar.i18n.dart';
import '../../utils/platform.dart';
import '../../widgets/controller.dart';
import 'package:flutter_quill/translations.dart';
import 'package:flutter_quill/extensions.dart';
import '../embed_types.dart';
class LinkDialog extends StatefulWidget {

@ -1,12 +1,7 @@
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:image_picker/image_picker.dart';
import '../../models/documents/nodes/embeddable.dart';
import '../../models/themes/quill_dialog_theme.dart';
import '../../models/themes/quill_icon_theme.dart';
import '../../widgets/controller.dart';
import '../../widgets/toolbar.dart';
import '../default_embed_builder.dart';
import '../embed_types.dart';
import 'image_video_utils.dart';

@ -0,0 +1,5 @@
import 'package:string_validator/string_validator.dart';
bool isImageBase64(String imageUrl) {
return !imageUrl.startsWith('http') && isBase64(imageUrl);
}

@ -2,12 +2,10 @@ import 'dart:convert';
import 'dart:io' as io;
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:photo_view/photo_view.dart';
import 'package:string_validator/string_validator.dart';
import '../../models/documents/attribute.dart';
import '../../models/documents/style.dart';
import '../../widgets/controller.dart';
import '../utils.dart';
const List<String> imageFileExtensions = [
'.jpeg',
@ -19,10 +17,6 @@ const List<String> imageFileExtensions = [
'.heic'
];
bool isImageBase64(String imageUrl) {
return !imageUrl.startsWith('http') && isBase64(imageUrl);
}
String getImageStyleString(QuillController controller) {
final String? s = controller
.getAllSelectionStyles()

@ -3,7 +3,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/scheduler.dart';
import '../../translations/toolbar.i18n.dart';
import 'package:flutter_quill/translations.dart';
class ImageResizer extends StatefulWidget {
const ImageResizer(

@ -2,11 +2,10 @@ import 'dart:io';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:video_player/video_player.dart';
import '../../../flutter_quill.dart';
/// Widget for playing back video
/// Refer to https://github.com/flutter/plugins/tree/master/packages/video_player/video_player
class VideoApp extends StatefulWidget {

@ -1,10 +1,9 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:youtube_player_flutter_quill/youtube_player_flutter_quill.dart';
import '../../widgets/default_styles.dart';
class YoutubeVideoApp extends StatefulWidget {
const YoutubeVideoApp(
{required this.videoUrl, required this.context, required this.readOnly});

@ -0,0 +1,18 @@
library flutter_quill_extensions;
import 'package:flutter_quill/flutter_quill.dart';
import 'embeds/builders.dart';
export 'embeds/toolbar.dart';
export 'embeds/builders.dart';
export 'embeds/embed_types.dart';
export 'embeds/utils.dart';
class FlutterQuillEmbeds {
static List<IEmbedBuilder> get builders => [
ImageEmbedBuilder(),
VideoEmbedBuilder(),
FormulaEmbedBuilder(),
];
}

@ -0,0 +1,36 @@
name: flutter_quill_extensions
description: Embed extensions for flutter_quill
version: 0.0.1
homepage: https://bulletjournal.us/home/index.html
#author: bulletjournal
repository: https://github.com/singerdmx/flutter-quill/flutter_quill_extensions
environment:
sdk: ">=2.12.0 <3.0.0"
flutter: ">=3.0.0"
dependencies:
flutter:
sdk: flutter
flutter_quill: ^6.0.0
image_picker: ^0.8.5+3
photo_view: ^0.14.0
video_player: ^2.4.2
youtube_player_flutter_quill: ^8.2.2
gallery_saver: ^2.3.2
math_keyboard: ^0.1.6
string_validator: ^0.3.0
dependency_overrides:
flutter_quill:
path: ../
dev_dependencies:
flutter_test:
sdk: flutter
pedantic: ^1.11.1
# The following section is specific to Flutter packages.
flutter:

@ -0,0 +1,6 @@
library flutter_quill.extensions;
export 'src/models/documents/nodes/leaf.dart' hide Text;
export 'src/models/rules/insert.dart';
export 'src/utils/platform.dart';
export 'src/utils/string.dart';

@ -1,9 +1,5 @@
library flutter_quill;
export 'src/embeds/default_embed_builder.dart';
export 'src/embeds/default_embed_toolbar.dart';
export 'src/embeds/embed_types.dart';
export 'src/embeds/widgets/image.dart';
export 'src/models/documents/attribute.dart';
export 'src/models/documents/document.dart';
export 'src/models/documents/nodes/embeddable.dart';
@ -18,6 +14,7 @@ export 'src/utils/embeds.dart';
export 'src/widgets/controller.dart';
export 'src/widgets/default_styles.dart';
export 'src/widgets/editor.dart';
export 'src/widgets/embeds.dart';
export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar.dart';

@ -10,17 +10,12 @@ 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';
import '../models/documents/style.dart';
import '../utils/platform.dart';
import 'box.dart';
import 'controller.dart';
import 'cursor.dart';
import 'default_styles.dart';
import 'delegate.dart';
import 'embeds.dart';
import 'float_cursor.dart';
import 'link.dart';
import 'raw_editor.dart';

@ -0,0 +1,25 @@
import 'package:flutter/material.dart';
import '../models/documents/nodes/leaf.dart' as leaf;
import '../models/themes/quill_dialog_theme.dart';
import '../models/themes/quill_icon_theme.dart';
import 'controller.dart';
abstract class IEmbedBuilder {
String get key;
Widget build(
BuildContext context,
QuillController controller,
leaf.Embed node,
bool readOnly,
void Function(GlobalKey videoContainerKey)? onVideoInit,
);
}
abstract class IEmbedToolbar {
Iterable<Widget> build(QuillController controller, double toolbarIconSize,
QuillIconTheme? iconTheme, QuillDialogTheme? dialogTheme);
bool get notEmpty;
}

@ -26,7 +26,6 @@ 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';

@ -1,7 +1,6 @@
import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.dart';
import '../embeds/default_embed_toolbar.dart';
import '../models/documents/attribute.dart';
import '../models/themes/quill_custom_button.dart';
import '../models/themes/quill_dialog_theme.dart';
@ -9,6 +8,7 @@ import '../models/themes/quill_icon_theme.dart';
import '../translations/toolbar.i18n.dart';
import '../utils/font.dart';
import 'controller.dart';
import 'embeds.dart';
import 'toolbar/arrow_indicated_button_list.dart';
import 'toolbar/clear_format_button.dart';
import 'toolbar/color_button.dart';

@ -0,0 +1,3 @@
library flutter_quill.translations;
export 'src/translations/toolbar.i18n.dart';

@ -15,22 +15,15 @@ dependencies:
collection: ^1.16.0
flutter_colorpicker: ^1.0.3
flutter_keyboard_visibility: ^5.2.0
image_picker: ^0.8.5+3
photo_view: ^0.14.0
quiver: ^3.1.0
string_validator: ^0.3.0
tuple: ^2.0.0
url_launcher: ^6.1.2
pedantic: ^1.11.1
video_player: ^2.4.2
characters: ^1.2.0
youtube_player_flutter_quill: ^8.2.2
diff_match_patch: ^0.4.1
i18n_extension: ^5.0.0
gallery_saver: ^2.3.2
device_info_plus: ^4.0.0
platform: ^3.1.0
math_keyboard: ^0.1.6
dev_dependencies:
flutter_test:

Loading…
Cancel
Save