The first steps of the major update

pull/1444/head
Ahmed Hnewa 2 years ago
parent 356c833d32
commit 4c1ec373b2
No known key found for this signature in database
GPG Key ID: C488CC70BBCEF0D1
  1. 3
      .github/PULL_REQUEST_TEMPLATE.md
  2. 4
      CHANGELOG.md
  3. 16
      README.md
  4. 13
      example/lib/main.dart
  5. 155
      example/lib/pages/home_page.dart
  6. 4
      example/lib/pages/read_only_page.dart
  7. 69
      example/lib/widgets/demo_scaffold.dart
  8. 2
      example/lib/widgets/time_stamp_embed_widget.dart
  9. 58
      example/pubspec.yaml
  10. 2
      flutter_quill_extensions/lib/embeds/builders.dart
  11. 2
      flutter_quill_extensions/lib/embeds/toolbar/camera_button.dart
  12. 2
      flutter_quill_extensions/lib/embeds/toolbar/image_video_utils.dart
  13. 2
      flutter_quill_extensions/lib/embeds/toolbar/media_button.dart
  14. 2
      lib/extensions.dart
  15. 2
      lib/flutter_quill.dart
  16. 42
      lib/src/core/quill_configurations.dart
  17. 3
      lib/src/models/documents/nodes/container.dart
  18. 24
      lib/src/models/documents/nodes/leaf.dart
  19. 8
      lib/src/models/documents/nodes/line.dart
  20. 2
      lib/src/test/widget_tester_extension.dart
  21. 16
      lib/src/utils/extensions/build_context.dart
  22. 28
      lib/src/widgets/editor.dart
  23. 68
      lib/src/widgets/raw_editor/raw_editor.dart
  24. 16
      lib/src/widgets/text_line.dart
  25. 719
      lib/src/widgets/toolbar.dart
  26. 17
      lib/src/widgets/utils/provider.dart
  27. 38
      pubspec.yaml
  28. 55
      test/bug_fix_test.dart
  29. 67
      test/widgets/editor_test.dart

@ -40,5 +40,6 @@ Closes #IssueNumber
- [ ] I have tested these changes locally. <!-- REQUIRED --> - [ ] I have tested these changes locally. <!-- REQUIRED -->
- [ ] I have followed the code style and guidelines. <!-- REQUIRED --> - [ ] I have followed the code style and guidelines. <!-- REQUIRED -->
- [ ] I have updated `CHANGELOG.md` with my changes in the next section <!-- REQUIRED --> - [ ] I have updated `CHANGELOG.md` with my changes in the next section <!-- REQUIRED -->
- [ ] I have run "dart format ." on the project <!-- REQUIRED --> - [ ] I have run `dart format .`` on the project <!-- REQUIRED -->
- [ ] I have run `dart fix --apply` on the project <!-- REQUIRED -->
- [ ] I have run `flutter test` and `flutter analyze` and it passed successfully <!-- REQUIRED --> - [ ] I have run `flutter test` and `flutter analyze` and it passed successfully <!-- REQUIRED -->

@ -1,3 +1,7 @@
# [7.4.17]
- **Breaking change**: The widgets `QuillEditor` and `QuillToolbar` are no longer have controller parameter, instead you need to make sure in the widget tree you have wrapped them with `QuillProvider` widget and provide the controller and the require configurations
# [7.4.16] # [7.4.16]
- Update documentation and README.md - Update documentation and README.md

@ -476,6 +476,22 @@ and then enter text using `quillEnterText`:
await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
``` ```
## License
[MIT](LICENSE)
## Contributors
Special thanks for everyone that have contributed to this project...
<a href="https://github.com/singerdmx/flutter-quill/graphs/contributors">
<img src="https://contrib.rocks/image?repo=singerdmx/flutter-quill" />
</a>
<br>
Made with [contrib.rocks](https://contrib.rocks).
## Sponsors ## Sponsors
<a href="https://bulletjournal.us/home/index.html"> <a href="https://bulletjournal.us/home/index.html">

@ -9,26 +9,13 @@ void main() {
} }
class MyApp extends StatelessWidget { class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MaterialApp( return MaterialApp(
debugShowCheckedModeBanner: false, debugShowCheckedModeBanner: false,
title: 'Quill Demo', title: 'Quill Demo',
theme: ThemeData( theme: ThemeData(
// This is the theme of your application.
//
// Try running your application with "flutter run". You'll see the
// application has a blue toolbar. Then, without quitting the app, try
// changing the primarySwatch below to Colors.green and then invoke
// "hot reload" (press "r" in the console where you ran "flutter run",
// or simply save your changes to "hot reload" in a Flutter IDE).
// Notice that the counter didn't reset back to zero; the application
// is not restarted.
primarySwatch: Colors.blue, primarySwatch: Colors.blue,
// This makes the visual density adapt to the platform that you run
// the app on. For desktop platforms, the controls will be smaller and
// closer together (more dense) than on mobile platforms.
visualDensity: VisualDensity.adaptivePlatformDensity, visualDensity: VisualDensity.adaptivePlatformDensity,
), ),
localizationsDelegates: [ localizationsDelegates: [

@ -9,9 +9,9 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path/path.dart'; import 'package:path/path.dart' as path;
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import '../universal_ui/universal_ui.dart'; import '../universal_ui/universal_ui.dart';
@ -61,7 +61,9 @@ class _HomePageState extends State<HomePage> {
final doc = Document()..insert(0, 'Empty asset'); final doc = Document()..insert(0, 'Empty asset');
setState(() { setState(() {
_controller = QuillController( _controller = QuillController(
document: doc, selection: const TextSelection.collapsed(offset: 0)); document: doc,
selection: const TextSelection.collapsed(offset: 0),
);
}); });
} }
} }
@ -174,9 +176,40 @@ class _HomePageState extends State<HomePage> {
}); });
} }
Widget _buildWelcomeEditor(BuildContext context) { QuillEditor get quillEditor {
Widget quillEditor = QuillEditor( if (kIsWeb) {
controller: _controller!, return QuillEditor(
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
readOnly: false,
placeholder: 'Add content',
expands: false,
padding: EdgeInsets.zero,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
),
embedBuilders: [
...defaultEmbedBuildersWeb,
TimeStampEmbedBuilderWidget()
],
);
}
return QuillEditor(
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, scrollable: true,
focusNode: _focusNode, focusNode: _focusNode,
@ -216,57 +249,11 @@ class _HomePageState extends State<HomePage> {
TimeStampEmbedBuilderWidget() TimeStampEmbedBuilderWidget()
], ],
); );
}
QuillToolbar get quillToolbar {
if (kIsWeb) { if (kIsWeb) {
quillEditor = QuillEditor( return QuillToolbar.basic(
controller: _controller!,
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
autoFocus: false,
readOnly: false,
placeholder: 'Add content',
expands: false,
padding: EdgeInsets.zero,
onTapUp: (details, p1) {
return _onTripleClickSelection();
},
customStyles: const DefaultStyles(
h1: DefaultTextBlockStyle(
TextStyle(
fontSize: 32,
color: Colors.black,
height: 1.15,
fontWeight: FontWeight.w300,
),
VerticalSpacing(16, 0),
VerticalSpacing(0, 0),
null),
sizeSmall: TextStyle(fontSize: 9),
),
embedBuilders: [
...defaultEmbedBuildersWeb,
TimeStampEmbedBuilderWidget()
]);
}
var toolbar = QuillToolbar.basic(
context: context,
embedButtons: FlutterQuillEmbeds.buttons(
// provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url.
// same goes for videos.
onImagePickCallback: _onImagePickCallback,
onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
),
showAlignmentButtons: true,
afterButtonPressed: _focusNode.requestFocus,
);
if (kIsWeb) {
toolbar = QuillToolbar.basic(
context: context,
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl, webImagePickImpl: _webImagePickImpl,
@ -276,8 +263,7 @@ class _HomePageState extends State<HomePage> {
); );
} }
if (_isDesktop()) { if (_isDesktop()) {
toolbar = QuillToolbar.basic( return QuillToolbar.basic(
context: context,
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback, onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop, filePickImpl: openFileSystemPickerForDesktop,
@ -286,7 +272,48 @@ class _HomePageState extends State<HomePage> {
afterButtonPressed: _focusNode.requestFocus, afterButtonPressed: _focusNode.requestFocus,
); );
} }
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
// provide a callback to enable picking images from device.
// if omit, "image" button only allows adding images from url.
// same goes for videos.
onImagePickCallback: _onImagePickCallback,
onVideoPickCallback: _onVideoPickCallback,
// uncomment to provide a custom "pick from" dialog.
// mediaPickSettingSelector: _selectMediaPickSetting,
// uncomment to provide a custom "pick from" dialog.
// cameraPickSettingSelector: _selectCameraPickSetting,
),
showAlignmentButtons: true,
afterButtonPressed: _focusNode.requestFocus,
);
}
Widget _buildWelcomeEditor(BuildContext context) {
// BUG in web!! should not releated to this pull request
///
/// EXCEPTION CAUGHT BY WIDGETS LIBRARY
///
// The following bool object was thrown building MediaQuery
//(MediaQueryData(size: Size(769.0, 1205.0),
// devicePixelRatio: 1.0, textScaleFactor: 1.0, platformBrightness:
//Brightness.dark, padding:
// EdgeInsets.zero, viewPadding: EdgeInsets.zero, viewInsets:
// EdgeInsets.zero,
// systemGestureInsets:
// EdgeInsets.zero, alwaysUse24HourFormat: false, accessibleNavigation:
// false,
// highContrast: false,
// disableAnimations: false, invertColors: false, boldText: false,
//navigationMode: traditional,
// gestureSettings: DeviceGestureSettings(touchSlop: null), displayFeatures:
// []
// )):
// false
// The relevant error-causing widget was:
// SafeArea
///
///
return SafeArea( return SafeArea(
child: QuillProvider( child: QuillProvider(
configurations: QuillConfigurations( configurations: QuillConfigurations(
@ -312,9 +339,9 @@ class _HomePageState extends State<HomePage> {
child: Container( child: Container(
padding: padding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 8), const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: toolbar, child: quillToolbar,
)) ))
: Container(child: toolbar) : Container(child: quillToolbar)
], ],
), ),
), ),
@ -339,7 +366,7 @@ class _HomePageState extends State<HomePage> {
// Copies the picked file from temporary cache to applications directory // Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory(); final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile = final copiedFile =
await file.copy('${appDocDir.path}/${basename(file.path)}'); await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString(); return copiedFile.path.toString();
} }
@ -364,7 +391,7 @@ class _HomePageState extends State<HomePage> {
// Copies the picked file from temporary cache to applications directory // Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory(); final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile = final copiedFile =
await file.copy('${appDocDir.path}/${basename(file.path)}'); await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString(); return copiedFile.path.toString();
} }
@ -462,8 +489,8 @@ class _HomePageState extends State<HomePage> {
// Saves the image to applications directory // Saves the image to applications directory
final appDocDir = await getApplicationDocumentsDirectory(); final appDocDir = await getApplicationDocumentsDirectory();
final file = await File( final file = await File(
'${appDocDir.path}/${basename('${DateTime.now().millisecondsSinceEpoch}.png')}') '${appDocDir.path}/${path.basename('${DateTime.now().millisecondsSinceEpoch}.png')}',
.writeAsBytes(imageBytes, flush: true); ).writeAsBytes(imageBytes, flush: true);
return file.path.toString(); return file.path.toString();
} }

@ -1,7 +1,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import '../universal_ui/universal_ui.dart'; import '../universal_ui/universal_ui.dart';
@ -35,7 +35,6 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
Widget _buildContent(BuildContext context, QuillController? controller) { Widget _buildContent(BuildContext context, QuillController? controller) {
var quillEditor = QuillEditor( var quillEditor = QuillEditor(
controller: controller!,
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, scrollable: true,
focusNode: _focusNode, focusNode: _focusNode,
@ -47,7 +46,6 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
); );
if (kIsWeb) { if (kIsWeb) {
quillEditor = QuillEditor( quillEditor = QuillEditor(
controller: controller,
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, scrollable: true,
focusNode: _focusNode, focusNode: _focusNode,

@ -5,7 +5,7 @@ import 'package:filesystem_picker/filesystem_picker.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; import 'package:flutter_quill_extensions/flutter_quill_extensions.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
@ -84,46 +84,51 @@ class _DemoScaffoldState extends State<DemoScaffold> {
); );
} }
@override QuillToolbar get quillToolbar {
Widget build(BuildContext context) {
if (_controller == null) {
return const Scaffold(body: Center(child: Text('Loading...')));
}
final actions = widget.actions ?? <Widget>[];
var toolbar = QuillToolbar.basic(
context: context,
embedButtons: FlutterQuillEmbeds.buttons(),
);
if (_isDesktop()) { if (_isDesktop()) {
toolbar = QuillToolbar.basic( return QuillToolbar.basic(
context: context,
embedButtons: FlutterQuillEmbeds.buttons( embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop, filePickImpl: openFileSystemPickerForDesktop,
), ),
); );
} }
return Scaffold( return QuillToolbar.basic(
key: _scaffoldKey, embedButtons: FlutterQuillEmbeds.buttons(),
appBar: AppBar( );
elevation: 0, }
backgroundColor: Theme.of(context).canvasColor,
centerTitle: false, @override
titleSpacing: 0, Widget build(BuildContext context) {
leading: IconButton( if (_controller == null) {
icon: Icon( return const Scaffold(body: Center(child: Text('Loading...')));
Icons.chevron_left, }
color: Colors.grey.shade800, final actions = widget.actions ?? <Widget>[];
size: 18,
return QuillProvider(
configurations: QuillConfigurations(controller: _controller!),
child: Scaffold(
key: _scaffoldKey,
appBar: AppBar(
elevation: 0,
backgroundColor: Theme.of(context).canvasColor,
centerTitle: false,
titleSpacing: 0,
leading: IconButton(
icon: Icon(
Icons.chevron_left,
color: Colors.grey.shade800,
size: 18,
),
onPressed: () => Navigator.pop(context),
), ),
onPressed: () => Navigator.pop(context), title: _loading || !widget.showToolbar ? null : quillToolbar,
actions: actions,
), ),
title: _loading || !widget.showToolbar ? null : toolbar, floatingActionButton: widget.floatingActionButton,
actions: actions, body: _loading
? const Center(child: Text('Loading...'))
: widget.builder(context, _controller),
), ),
floatingActionButton: widget.floatingActionButton,
body: _loading
? const Center(child: Text('Loading...'))
: widget.builder(context, _controller),
); );
} }

@ -1,7 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
class TimeStampEmbed extends Embeddable { class TimeStampEmbed extends Embeddable {
const TimeStampEmbed( const TimeStampEmbed(

@ -1,20 +1,6 @@
name: app name: app
description: demo app description: demo app
publish_to: 'none'
# The following line prevents the package from being accidentally published to
# pub.dev using `pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.0.0+1 version: 1.0.0+1
environment: environment:
@ -25,8 +11,6 @@ dependencies:
sdk: flutter sdk: flutter
universal_html: ^2.2.4 universal_html: ^2.2.4
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.6 cupertino_icons: ^1.0.6
path_provider: ^2.1.1 path_provider: ^2.1.1
filesystem_picker: ^4.0.0 filesystem_picker: ^4.0.0
@ -44,21 +28,9 @@ dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter: flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true uses-material-design: true
# To add assets to your application, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
assets: assets:
- assets/ - assets/
@ -89,30 +61,4 @@ flutter:
- asset: assets/fonts/RobotoMono-Regular.ttf - asset: assets/fonts/RobotoMono-Regular.ttf
- family: SF-UI-Display - family: SF-UI-Display
fonts: fonts:
- asset: assets/fonts/SF-Pro-Display-Regular.otf - asset: assets/fonts/SF-Pro-Display-Regular.otf
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# For details regarding adding assets from package dependencies, see
# https://flutter.dev/assets-and-images/#from-packages
# To add custom fonts to your application, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts from package dependencies,
# see https://flutter.dev/custom-fonts/#from-packages

@ -5,7 +5,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.dart' as base; import 'package:flutter_quill/extensions.dart' as base;
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill/translations.dart'; import 'package:flutter_quill/translations.dart';
import 'package:math_keyboard/math_keyboard.dart'; import 'package:math_keyboard/math_keyboard.dart';
import 'package:universal_html/html.dart' as html; import 'package:universal_html/html.dart' as html;

@ -1,5 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill/translations.dart'; import 'package:flutter_quill/translations.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';

@ -3,7 +3,7 @@ import 'dart:io' show File;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill/translations.dart'; import 'package:flutter_quill/translations.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';

@ -4,7 +4,7 @@ import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_quill/extensions.dart'; import 'package:flutter_quill/extensions.dart';
import 'package:flutter_quill/flutter_quill.dart' hide Text; import 'package:flutter_quill/flutter_quill.dart' hide QuillText;
import 'package:flutter_quill/translations.dart'; import 'package:flutter_quill/translations.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';

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

@ -28,4 +28,4 @@ export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
export 'src/widgets/style_widgets/style_widgets.dart'; export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar.dart'; export 'src/widgets/toolbar.dart';
export 'src/widgets/toolbar/enum.dart'; export 'src/widgets/toolbar/enum.dart';
export 'src/widgets/utils/quill_provider.dart'; export 'src/widgets/utils/provider.dart';

@ -1,12 +1,54 @@
import 'package:flutter/foundation.dart' show immutable; import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/material.dart' show Color, Colors;
import '../../flutter_quill.dart'; import '../../flutter_quill.dart';
// I will start on this in the major-update-2
@immutable
class QuillToolbarConfigurations {
const QuillToolbarConfigurations();
}
///
@immutable
class QuillEditorConfigurations {
const QuillEditorConfigurations();
}
/// The shared configurations between [QuillEditorConfigurations] and
/// [QuillToolbarConfigurations] so we don't duplicate things
class QuillSharedConfigurations {
const QuillSharedConfigurations({
this.dialogBarrierColor = Colors.black54,
});
// This is just example or showcase of this major update to make the library
// more maintanable, flexible, and customizable
/// The barrier color of the shown dialogs
final Color dialogBarrierColor;
}
@immutable @immutable
class QuillConfigurations { class QuillConfigurations {
const QuillConfigurations({ const QuillConfigurations({
required this.controller, required this.controller,
this.editorConfigurations = const QuillEditorConfigurations(),
this.toolbarConfigurations = const QuillToolbarConfigurations(),
this.sharedConfigurations = const QuillSharedConfigurations(),
}); });
/// Controller object which establishes a link between a rich text document
/// and this editor.
///
/// The controller is shared between [QuillEditorConfigurations] and
/// [QuillToolbarConfigurations] but to simplify things we will defined it
/// here, it should not be null
final QuillController controller; final QuillController controller;
final QuillEditorConfigurations editorConfigurations;
final QuillToolbarConfigurations toolbarConfigurations;
final QuillSharedConfigurations sharedConfigurations;
} }

@ -12,7 +12,8 @@ import 'node.dart';
/// operation container looks for a child at specified index position and /// operation container looks for a child at specified index position and
/// forwards operation to that child. /// forwards operation to that child.
/// ///
/// Most of the operation handling logic is implemented by [Line] and [Text]. /// Most of the operation handling logic is implemented by [Line]
/// and [QuillText].
abstract class Container<T extends Node?> extends Node { abstract class Container<T extends Node?> extends Node {
final LinkedList<Node> _children = LinkedList<Node>(); final LinkedList<Node> _children = LinkedList<Node>();

@ -16,12 +16,12 @@ abstract class Leaf extends Node {
} }
final text = data as String; final text = data as String;
assert(text.isNotEmpty); assert(text.isNotEmpty);
return Text(text); return QuillText(text);
} }
Leaf.val(Object val) : _value = val; Leaf.val(Object val) : _value = val;
/// Contents of this node, either a String if this is a [Text] or an /// Contents of this node, either a String if this is a [QuillText] or an
/// [Embed] if this is an [BlockEmbed]. /// [Embed] if this is an [BlockEmbed].
Object get value => _value; Object get value => _value;
Object _value; Object _value;
@ -119,11 +119,11 @@ abstract class Leaf extends Node {
} }
// This is a text node and it can only be merged with other text nodes. // This is a text node and it can only be merged with other text nodes.
var node = this as Text; var node = this as QuillText;
// Merging it with previous node if style is the same. // Merging it with previous node if style is the same.
final prev = node.previous; final prev = node.previous;
if (!node.isFirst && prev is Text && prev.style == node.style) { if (!node.isFirst && prev is QuillText && prev.style == node.style) {
prev._value = prev.value + node.value; prev._value = prev.value + node.value;
node.unlink(); node.unlink();
node = prev; node = prev;
@ -131,7 +131,7 @@ abstract class Leaf extends Node {
// Merging it with next node if style is the same. // Merging it with next node if style is the same.
final next = node.next; final next = node.next;
if (!node.isLast && next is Text && next.style == node.style) { if (!node.isLast && next is QuillText && next.style == node.style) {
node._value = node.value + next.value; node._value = node.value + next.value;
next.unlink(); next.unlink();
} }
@ -157,7 +157,7 @@ abstract class Leaf extends Node {
return isLast ? null : next as Leaf?; return isLast ? null : next as Leaf?;
} }
assert(this is Text); assert(this is QuillText);
final text = _value as String; final text = _value as String;
_value = text.substring(0, index); _value = text.substring(0, index);
final split = Leaf(text.substring(index))..applyStyle(style); final split = Leaf(text.substring(index))..applyStyle(style);
@ -211,13 +211,17 @@ abstract class Leaf extends Node {
/// ///
/// * [Embed], a leaf node representing an embeddable object. /// * [Embed], a leaf node representing an embeddable object.
/// * [Line], a node representing a line of text. /// * [Line], a node representing a line of text.
class Text extends Leaf { ///
Text([String text = '']) /// Update:
/// The reason we are renamed quill [Text] to [QuillText] so it doesn't
/// conflict with the one from the widgets, material or cupertino library
class QuillText extends Leaf {
QuillText([String text = ''])
: assert(!text.contains('\n')), : assert(!text.contains('\n')),
super.val(text); super.val(text);
@override @override
Node newInstance() => Text(value); Node newInstance() => QuillText(value);
@override @override
String get value => _value as String; String get value => _value as String;
@ -232,7 +236,7 @@ class Text extends Leaf {
/// An embed node inside of a line in a Quill document. /// An embed node inside of a line in a Quill document.
/// ///
/// Embed node is a leaf node similar to [Text]. It represents an arbitrary /// Embed node is a leaf node similar to [QuillText]. It represents an arbitrary
/// piece of non-textual content embedded into a document, such as, image, /// piece of non-textual content embedded into a document, such as, image,
/// horizontal rule, video, or any other object with defined structure, /// horizontal rule, video, or any other object with defined structure,
/// like a tweet, for instance. /// like a tweet, for instance.

@ -15,13 +15,13 @@ import 'node.dart';
/// A line of rich text in a Quill document. /// A line of rich text in a Quill document.
/// ///
/// Line serves as a container for [Leaf]s, like [Text] and [Embed]. /// Line serves as a container for [Leaf]s, like [QuillText] and [Embed].
/// ///
/// When a line contains an embed, it fully occupies the line, no other embeds /// When a line contains an embed, it fully occupies the line, no other embeds
/// or text nodes are allowed. /// or text nodes are allowed.
class Line extends Container<Leaf?> { class Line extends Container<Leaf?> {
@override @override
Leaf get defaultChild => Text(); Leaf get defaultChild => QuillText();
@override @override
int get length => super.length + 1; int get length => super.length + 1;
@ -400,14 +400,14 @@ class Line extends Container<Leaf?> {
if (node != null) { if (node != null) {
var pos = 0; var pos = 0;
pos = node.length - data.offset; pos = node.length - data.offset;
if (node is Text && node.style.isNotEmpty) { if (node is QuillText && node.style.isNotEmpty) {
result.add(OffsetValue(beg, node.style, node.length)); result.add(OffsetValue(beg, node.style, node.length));
} else if (node.value is Embeddable) { } else if (node.value is Embeddable) {
result.add(OffsetValue(beg, node.value as Embeddable, node.length)); result.add(OffsetValue(beg, node.value as Embeddable, node.length));
} }
while (!node!.isLast && pos < local) { while (!node!.isLast && pos < local) {
node = node.next as Leaf; node = node.next as Leaf;
if (node is Text && node.style.isNotEmpty) { if (node is QuillText && node.style.isNotEmpty) {
result.add(OffsetValue(pos + beg, node.style, node.length)); result.add(OffsetValue(pos + beg, node.style, node.length));
} else if (node.value is Embeddable) { } else if (node.value is Embeddable) {
result.add( result.add(

@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../widgets/editor.dart'; import '../widgets/editor.dart';
import '../widgets/raw_editor.dart'; import '../widgets/raw_editor/raw_editor.dart';
/// Extends /// Extends
extension QuillEnterText on WidgetTester { extension QuillEnterText on WidgetTester {

@ -18,4 +18,20 @@ extension BuildContextExt on BuildContext {
QuillController get requireQuillController { QuillController get requireQuillController {
return requireQuillProvider.configurations.controller; return requireQuillProvider.configurations.controller;
} }
QuillConfigurations get requireQuillConfigurations {
return requireQuillProvider.configurations;
}
QuillConfigurations? get quillConfigurations {
return quillProvider?.configurations;
}
QuillSharedConfigurations? get sharedQuillConfigurations {
return quillConfigurations?.sharedConfigurations;
}
QuillSharedConfigurations get requireSharedQuillConfigurations {
return requireQuillConfigurations.sharedConfigurations;
}
} }

@ -11,21 +11,16 @@ import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:i18n_extension/i18n_widget.dart'; import 'package:i18n_extension/i18n_widget.dart';
import '../models/documents/document.dart'; import '../../flutter_quill.dart';
import '../models/documents/nodes/container.dart' as container_node; import '../models/documents/nodes/container.dart' as container_node;
import '../models/documents/nodes/leaf.dart'; import '../utils/extensions/build_context.dart';
import '../models/structs/offset_value.dart';
import '../models/themes/quill_dialog_theme.dart';
import '../utils/platform.dart'; import '../utils/platform.dart';
import 'box.dart'; import 'box.dart';
import 'controller.dart';
import 'cursor.dart'; import 'cursor.dart';
import 'default_styles.dart';
import 'delegate.dart'; import 'delegate.dart';
import 'embeds.dart';
import 'float_cursor.dart'; import 'float_cursor.dart';
import 'link.dart'; import 'link.dart';
import 'raw_editor.dart'; import 'raw_editor/raw_editor.dart';
import 'text_selection.dart'; import 'text_selection.dart';
/// Base interface for the editor state which defines contract used by /// Base interface for the editor state which defines contract used by
@ -148,7 +143,6 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics {
class QuillEditor extends StatefulWidget { class QuillEditor extends StatefulWidget {
const QuillEditor({ const QuillEditor({
required this.controller,
required this.focusNode, required this.focusNode,
required this.scrollController, required this.scrollController,
required this.scrollable, required this.scrollable,
@ -198,7 +192,6 @@ class QuillEditor extends StatefulWidget {
}) : super(key: key); }) : super(key: key);
factory QuillEditor.basic({ factory QuillEditor.basic({
required QuillController controller,
required bool readOnly, required bool readOnly,
TextSelectionThemeData? textSelectionThemeData, TextSelectionThemeData? textSelectionThemeData,
Brightness? keyboardAppearance, Brightness? keyboardAppearance,
@ -215,7 +208,6 @@ class QuillEditor extends StatefulWidget {
Locale? locale, Locale? locale,
}) { }) {
return QuillEditor( return QuillEditor(
controller: controller,
scrollController: ScrollController(), scrollController: ScrollController(),
scrollable: true, scrollable: true,
focusNode: focusNode ?? FocusNode(), focusNode: focusNode ?? FocusNode(),
@ -232,11 +224,7 @@ class QuillEditor extends StatefulWidget {
); );
} }
/// Controller object which establishes a link between a rich text document // final QuillController controller;
/// and this editor.
///
/// Must not be null.
final QuillController controller;
/// Controls whether this editor has keyboard focus. /// Controls whether this editor has keyboard focus.
final FocusNode focusNode; final FocusNode focusNode;
@ -523,7 +511,7 @@ class QuillEditorState extends State<QuillEditor>
final child = RawEditor( final child = RawEditor(
key: _editorKey, key: _editorKey,
controller: widget.controller, controller: context.requireQuillController,
focusNode: widget.focusNode, focusNode: widget.focusNode,
scrollController: widget.scrollController, scrollController: widget.scrollController,
scrollable: widget.scrollable, scrollable: widget.scrollable,
@ -692,7 +680,7 @@ class _QuillEditorSelectionGestureDetectorBuilder
} }
bool _isPositionSelected(TapUpDetails details) { bool _isPositionSelected(TapUpDetails details) {
if (_state.widget.controller.document.isEmpty()) { if (_state.context.requireQuillController.document.isEmpty()) {
return false; return false;
} }
final pos = renderEditor!.getPositionForOffset(details.globalPosition); final pos = renderEditor!.getPositionForOffset(details.globalPosition);
@ -705,7 +693,9 @@ class _QuillEditorSelectionGestureDetectorBuilder
final segmentLeaf = result.leaf; final segmentLeaf = result.leaf;
if (segmentLeaf == null && line.length == 1) { if (segmentLeaf == null && line.length == 1) {
editor!.widget.controller.updateSelection( editor!.widget.controller.updateSelection(
TextSelection.collapsed(offset: pos.offset), ChangeSource.LOCAL); TextSelection.collapsed(offset: pos.offset),
ChangeSource.LOCAL,
);
return true; return true;
} }
return false; return false;

@ -24,36 +24,36 @@ import 'package:flutter_keyboard_visibility/flutter_keyboard_visibility.dart'
show KeyboardVisibilityController; show KeyboardVisibilityController;
import 'package:pasteboard/pasteboard.dart' show Pasteboard; import 'package:pasteboard/pasteboard.dart' show Pasteboard;
import '../models/documents/attribute.dart'; import '../../models/documents/attribute.dart';
import '../models/documents/document.dart'; import '../../models/documents/document.dart';
import '../models/documents/nodes/block.dart'; import '../../models/documents/nodes/block.dart';
import '../models/documents/nodes/embeddable.dart'; import '../../models/documents/nodes/embeddable.dart';
import '../models/documents/nodes/leaf.dart' as leaf; import '../../models/documents/nodes/leaf.dart' as leaf;
import '../models/documents/nodes/line.dart'; import '../../models/documents/nodes/line.dart';
import '../models/documents/nodes/node.dart'; import '../../models/documents/nodes/node.dart';
import '../models/structs/offset_value.dart'; import '../../models/structs/offset_value.dart';
import '../models/structs/vertical_spacing.dart'; import '../../models/structs/vertical_spacing.dart';
import '../models/themes/quill_dialog_theme.dart'; import '../../models/themes/quill_dialog_theme.dart';
import '../utils/cast.dart'; import '../../utils/cast.dart';
import '../utils/delta.dart'; import '../../utils/delta.dart';
import '../utils/embeds.dart'; import '../../utils/embeds.dart';
import '../utils/platform.dart'; import '../../utils/platform.dart';
import 'controller.dart'; import '../controller.dart';
import 'cursor.dart'; import '../cursor.dart';
import 'default_styles.dart'; import '../default_styles.dart';
import 'delegate.dart'; import '../delegate.dart';
import 'editor.dart'; import '../editor.dart';
import 'keyboard_listener.dart'; import '../keyboard_listener.dart';
import 'link.dart'; import '../link.dart';
import 'proxy.dart'; import '../proxy.dart';
import 'quill_single_child_scroll_view.dart'; import '../quill_single_child_scroll_view.dart';
import 'raw_editor/raw_editor_state_selection_delegate_mixin.dart'; import '../text_block.dart';
import 'raw_editor/raw_editor_state_text_input_client_mixin.dart'; import '../text_line.dart';
import 'text_block.dart'; import '../text_selection.dart';
import 'text_line.dart'; import '../toolbar/link_style_button2.dart';
import 'text_selection.dart'; import '../toolbar/search_dialog.dart';
import 'toolbar/link_style_button2.dart'; import 'raw_editor_state_selection_delegate_mixin.dart';
import 'toolbar/search_dialog.dart'; import 'raw_editor_state_text_input_client_mixin.dart';
class RawEditor extends StatefulWidget { class RawEditor extends StatefulWidget {
const RawEditor({ const RawEditor({
@ -749,7 +749,7 @@ class RawEditorState extends EditorState
return KeyEventResult.ignored; return KeyEventResult.ignored;
} }
final text = castOrNull<leaf.Text>(line.first); final text = castOrNull<leaf.QuillText>(line.first);
if (text == null) { if (text == null) {
return KeyEventResult.ignored; return KeyEventResult.ignored;
} }
@ -805,7 +805,7 @@ class RawEditorState extends EditorState
return insertTabCharacter(); return insertTabCharacter();
} }
if (node is! Line || (node.isNotEmpty && node.first is! leaf.Text)) { if (node is! Line || (node.isNotEmpty && node.first is! leaf.QuillText)) {
return insertTabCharacter(); return insertTabCharacter();
} }
@ -814,7 +814,7 @@ class RawEditorState extends EditorState
parentBlock.style.containsKey(Attribute.ul.key) || parentBlock.style.containsKey(Attribute.ul.key) ||
parentBlock.style.containsKey(Attribute.checked.key)) { parentBlock.style.containsKey(Attribute.checked.key)) {
if (node.isNotEmpty && if (node.isNotEmpty &&
(node.first as leaf.Text).value.isNotEmpty && (node.first as leaf.QuillText).value.isNotEmpty &&
controller.selection.base.offset > node.documentOffset) { controller.selection.base.offset > node.documentOffset) {
return insertTabCharacter(); return insertTabCharacter();
} }
@ -822,7 +822,7 @@ class RawEditorState extends EditorState
return KeyEventResult.handled; return KeyEventResult.handled;
} }
if (node.isNotEmpty && (node.first as leaf.Text).value.isNotEmpty) { if (node.isNotEmpty && (node.first as leaf.QuillText).value.isNotEmpty) {
return insertTabCharacter(); return insertTabCharacter();
} }

@ -240,7 +240,7 @@ class _TextLineState extends State<TextLine> {
TextSpan _buildTextSpan(DefaultStyles defaultStyles, LinkedList<Node> nodes, TextSpan _buildTextSpan(DefaultStyles defaultStyles, LinkedList<Node> nodes,
TextStyle lineStyle) { TextStyle lineStyle) {
if (nodes.isEmpty && kIsWeb) { if (nodes.isEmpty && kIsWeb) {
nodes = LinkedList<Node>()..add(leaf.Text('\u{200B}')); nodes = LinkedList<Node>()..add(leaf.QuillText('\u{200B}'));
} }
final children = nodes final children = nodes
.map((node) => .map((node) =>
@ -307,7 +307,7 @@ class _TextLineState extends State<TextLine> {
TextSpan _getTextSpanFromNode( TextSpan _getTextSpanFromNode(
DefaultStyles defaultStyles, Node node, Style lineStyle) { DefaultStyles defaultStyles, Node node, Style lineStyle) {
final textNode = node as leaf.Text; final textNode = node as leaf.QuillText;
final nodeStyle = textNode.style; final nodeStyle = textNode.style;
final isLink = nodeStyle.containsKey(Attribute.link.key) && final isLink = nodeStyle.containsKey(Attribute.link.key) &&
nodeStyle.attributes[Attribute.link.key]!.value != null; nodeStyle.attributes[Attribute.link.key]!.value != null;
@ -323,8 +323,12 @@ class _TextLineState extends State<TextLine> {
); );
} }
TextStyle _getInlineTextStyle(leaf.Text textNode, DefaultStyles defaultStyles, TextStyle _getInlineTextStyle(
Style nodeStyle, Style lineStyle, bool isLink) { leaf.QuillText textNode,
DefaultStyles defaultStyles,
Style nodeStyle,
Style lineStyle,
bool isLink) {
var res = const TextStyle(); // This is inline text style var res = const TextStyle(); // This is inline text style
final color = textNode.style.attributes[Attribute.color.key]; final color = textNode.style.attributes[Attribute.color.key];
@ -413,7 +417,7 @@ class _TextLineState extends State<TextLine> {
} }
if (widget.customRecognizerBuilder != null) { if (widget.customRecognizerBuilder != null) {
final textNode = segment as leaf.Text; final textNode = segment as leaf.QuillText;
final nodeStyle = textNode.style; final nodeStyle = textNode.style;
nodeStyle.attributes.forEach((key, value) { nodeStyle.attributes.forEach((key, value) {
@ -1097,7 +1101,7 @@ class RenderEditableTextLine extends RenderEditableBox {
if (inlineCodeStyle.backgroundColor != null) { if (inlineCodeStyle.backgroundColor != null) {
for (final item in line.children) { for (final item in line.children) {
if (item is! leaf.Text || if (item is! leaf.QuillText ||
!item.style.containsKey(Attribute.inlineCode.key)) { !item.style.containsKey(Attribute.inlineCode.key)) {
continue; continue;
} }

@ -2,30 +2,9 @@ import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.dart'; import 'package:i18n_extension/i18n_widget.dart';
import '../../flutter_quill.dart'; import '../../flutter_quill.dart';
import '../models/documents/attribute.dart';
import '../models/structs/link_dialog_action.dart';
import '../models/themes/quill_custom_button.dart';
import '../models/themes/quill_dialog_theme.dart';
import '../models/themes/quill_icon_theme.dart';
import '../translations/toolbar.i18n.dart'; import '../translations/toolbar.i18n.dart';
import '../utils/extensions/build_context.dart'; import '../utils/extensions/build_context.dart';
import 'controller.dart';
import 'embeds.dart';
import 'toolbar/arrow_indicated_button_list.dart'; import 'toolbar/arrow_indicated_button_list.dart';
import 'toolbar/clear_format_button.dart';
import 'toolbar/color_button.dart';
import 'toolbar/custom_button.dart';
import 'toolbar/enum.dart';
import 'toolbar/history_button.dart';
import 'toolbar/indent_button.dart';
import 'toolbar/link_style_button.dart';
import 'toolbar/quill_font_family_button.dart';
import 'toolbar/quill_font_size_button.dart';
import 'toolbar/search_button.dart';
import 'toolbar/select_alignment_button.dart';
import 'toolbar/select_header_style_button.dart';
import 'toolbar/toggle_check_list_button.dart';
import 'toolbar/toggle_style_button.dart';
export 'toolbar/clear_format_button.dart'; export 'toolbar/clear_format_button.dart';
export 'toolbar/color_button.dart'; export 'toolbar/color_button.dart';
@ -52,9 +31,13 @@ const double kIconButtonFactor = 1.77;
/// The horizontal margin between the contents of each toolbar section. /// The horizontal margin between the contents of each toolbar section.
const double kToolbarSectionSpacing = 4; const double kToolbarSectionSpacing = 4;
typedef QuillToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
);
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget { class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({ const QuillToolbar({
required this.children, required this.childrenBuilder,
this.axis = Axis.horizontal, this.axis = Axis.horizontal,
this.toolbarSize = kDefaultIconSize * 2, this.toolbarSize = kDefaultIconSize * 2,
this.toolbarSectionSpacing = kToolbarSectionSpacing, this.toolbarSectionSpacing = kToolbarSectionSpacing,
@ -73,7 +56,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
}) : super(key: key); }) : super(key: key);
factory QuillToolbar.basic({ factory QuillToolbar.basic({
required BuildContext context,
Axis axis = Axis.horizontal, Axis axis = Axis.horizontal,
double toolbarIconSize = kDefaultIconSize, double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = kToolbarSectionSpacing, double toolbarSectionSpacing = kToolbarSectionSpacing,
@ -250,8 +232,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
ToolbarButtons.search: 'Search'.i18n, ToolbarButtons.search: 'Search'.i18n,
}; };
final controller = context.requireQuillController;
return QuillToolbar( return QuillToolbar(
key: key, key: key,
axis: axis, axis: axis,
@ -265,349 +245,370 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
customButtons: customButtons, customButtons: customButtons,
locale: locale, locale: locale,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
children: [ childrenBuilder: (context) {
if (showUndo) final controller = context.requireQuillController;
HistoryButton(
icon: Icons.undo_outlined, return [
iconSize: toolbarIconSize, if (showUndo)
tooltip: buttonTooltips[ToolbarButtons.undo], HistoryButton(
controller: controller, icon: Icons.undo_outlined,
undo: true, iconSize: toolbarIconSize,
iconTheme: iconTheme, tooltip: buttonTooltips[ToolbarButtons.undo],
afterButtonPressed: afterButtonPressed, controller: controller,
), undo: true,
if (showRedo) iconTheme: iconTheme,
HistoryButton( afterButtonPressed: afterButtonPressed,
icon: Icons.redo_outlined, ),
iconSize: toolbarIconSize, if (showRedo)
tooltip: buttonTooltips[ToolbarButtons.redo], HistoryButton(
controller: controller, icon: Icons.redo_outlined,
undo: false, iconSize: toolbarIconSize,
iconTheme: iconTheme, tooltip: buttonTooltips[ToolbarButtons.redo],
afterButtonPressed: afterButtonPressed, controller: controller,
), undo: false,
if (showFontFamily) iconTheme: iconTheme,
QuillFontFamilyButton( afterButtonPressed: afterButtonPressed,
iconTheme: iconTheme, ),
iconSize: toolbarIconSize, if (showFontFamily)
tooltip: buttonTooltips[ToolbarButtons.fontFamily], QuillFontFamilyButton(
attribute: Attribute.font, iconTheme: iconTheme,
controller: controller, iconSize: toolbarIconSize,
rawItemsMap: fontFamilies, tooltip: buttonTooltips[ToolbarButtons.fontFamily],
afterButtonPressed: afterButtonPressed, attribute: Attribute.font,
), controller: controller,
if (showFontSize) rawItemsMap: fontFamilies,
QuillFontSizeButton( afterButtonPressed: afterButtonPressed,
iconTheme: iconTheme, ),
iconSize: toolbarIconSize, if (showFontSize)
tooltip: buttonTooltips[ToolbarButtons.fontSize], QuillFontSizeButton(
attribute: Attribute.size, iconTheme: iconTheme,
controller: controller, iconSize: toolbarIconSize,
rawItemsMap: fontSizes, tooltip: buttonTooltips[ToolbarButtons.fontSize],
afterButtonPressed: afterButtonPressed, attribute: Attribute.size,
), controller: controller,
if (showBoldButton) rawItemsMap: fontSizes,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.bold, ),
icon: Icons.format_bold, if (showBoldButton)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.bold], attribute: Attribute.bold,
controller: controller, icon: Icons.format_bold,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.bold],
), controller: controller,
if (showSubscript) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.subscript, ),
icon: Icons.subscript, if (showSubscript)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.subscript], attribute: Attribute.subscript,
controller: controller, icon: Icons.subscript,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.subscript],
), controller: controller,
if (showSuperscript) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.superscript, ),
icon: Icons.superscript, if (showSuperscript)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.superscript], attribute: Attribute.superscript,
controller: controller, icon: Icons.superscript,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.superscript],
), controller: controller,
if (showItalicButton) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.italic, ),
icon: Icons.format_italic, if (showItalicButton)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.italic], attribute: Attribute.italic,
controller: controller, icon: Icons.format_italic,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.italic],
), controller: controller,
if (showSmallButton) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.small, ),
icon: Icons.format_size, if (showSmallButton)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.small], attribute: Attribute.small,
controller: controller, icon: Icons.format_size,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.small],
), controller: controller,
if (showUnderLineButton) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.underline, ),
icon: Icons.format_underline, if (showUnderLineButton)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.underline], attribute: Attribute.underline,
controller: controller, icon: Icons.format_underline,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.underline],
), controller: controller,
if (showStrikeThrough) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.strikeThrough, ),
icon: Icons.format_strikethrough, if (showStrikeThrough)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.strikeThrough], attribute: Attribute.strikeThrough,
controller: controller, icon: Icons.format_strikethrough,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.strikeThrough],
), controller: controller,
if (showInlineCode) iconTheme: iconTheme,
ToggleStyleButton( afterButtonPressed: afterButtonPressed,
attribute: Attribute.inlineCode, ),
icon: Icons.code, if (showInlineCode)
iconSize: toolbarIconSize, ToggleStyleButton(
tooltip: buttonTooltips[ToolbarButtons.inlineCode], attribute: Attribute.inlineCode,
controller: controller, icon: Icons.code,
iconTheme: iconTheme, iconSize: toolbarIconSize,
afterButtonPressed: afterButtonPressed, tooltip: buttonTooltips[ToolbarButtons.inlineCode],
), controller: controller,
if (showColorButton) iconTheme: iconTheme,
ColorButton( afterButtonPressed: afterButtonPressed,
icon: Icons.color_lens, ),
iconSize: toolbarIconSize, if (showColorButton)
tooltip: buttonTooltips[ToolbarButtons.color], ColorButton(
controller: controller, icon: Icons.color_lens,
background: false, iconSize: toolbarIconSize,
iconTheme: iconTheme, tooltip: buttonTooltips[ToolbarButtons.color],
afterButtonPressed: afterButtonPressed, controller: controller,
), background: false,
if (showBackgroundColorButton) iconTheme: iconTheme,
ColorButton( afterButtonPressed: afterButtonPressed,
icon: Icons.format_color_fill, dialogBarrierColor:
iconSize: toolbarIconSize, context.requireSharedQuillConfigurations.dialogBarrierColor,
tooltip: buttonTooltips[ToolbarButtons.backgroundColor], ),
controller: controller, if (showBackgroundColorButton)
background: true, ColorButton(
iconTheme: iconTheme, icon: Icons.format_color_fill,
afterButtonPressed: afterButtonPressed, iconSize: toolbarIconSize,
), tooltip: buttonTooltips[ToolbarButtons.backgroundColor],
if (showClearFormat) controller: controller,
ClearFormatButton( background: true,
icon: Icons.format_clear, iconTheme: iconTheme,
iconSize: toolbarIconSize, afterButtonPressed: afterButtonPressed,
tooltip: buttonTooltips[ToolbarButtons.clearFormat], dialogBarrierColor:
controller: controller, context.requireSharedQuillConfigurations.dialogBarrierColor,
iconTheme: iconTheme, ),
afterButtonPressed: afterButtonPressed, if (showClearFormat)
), ClearFormatButton(
if (embedButtons != null) icon: Icons.format_clear,
for (final builder in embedButtons) iconSize: toolbarIconSize,
builder(controller, toolbarIconSize, iconTheme, dialogTheme), tooltip: buttonTooltips[ToolbarButtons.clearFormat],
if (showDividers && controller: controller,
isButtonGroupShown[0] && iconTheme: iconTheme,
(isButtonGroupShown[1] || afterButtonPressed: afterButtonPressed,
isButtonGroupShown[2] || ),
isButtonGroupShown[3] || if (embedButtons != null)
isButtonGroupShown[4] || for (final builder in embedButtons)
isButtonGroupShown[5])) builder(controller, toolbarIconSize, iconTheme, dialogTheme),
QuillDivider(axis, if (showDividers &&
color: sectionDividerColor, space: sectionDividerSpace), isButtonGroupShown[0] &&
if (showAlignmentButtons) (isButtonGroupShown[1] ||
SelectAlignmentButton( isButtonGroupShown[2] ||
controller: controller, isButtonGroupShown[3] ||
tooltips: Map.of(buttonTooltips) isButtonGroupShown[4] ||
..removeWhere((key, value) => ![ isButtonGroupShown[5]))
ToolbarButtons.leftAlignment, QuillDivider(
ToolbarButtons.centerAlignment, axis,
ToolbarButtons.rightAlignment, color: sectionDividerColor,
ToolbarButtons.justifyAlignment, space: sectionDividerSpace,
].contains(key)), ),
iconSize: toolbarIconSize, if (showAlignmentButtons)
iconTheme: iconTheme, SelectAlignmentButton(
showLeftAlignment: showLeftAlignment, controller: controller,
showCenterAlignment: showCenterAlignment, tooltips: Map.of(buttonTooltips)
showRightAlignment: showRightAlignment, ..removeWhere((key, value) => ![
showJustifyAlignment: showJustifyAlignment, ToolbarButtons.leftAlignment,
afterButtonPressed: afterButtonPressed, ToolbarButtons.centerAlignment,
), ToolbarButtons.rightAlignment,
if (showDirection) ToolbarButtons.justifyAlignment,
ToggleStyleButton( ].contains(key)),
attribute: Attribute.rtl, iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.direction], iconTheme: iconTheme,
controller: controller, showLeftAlignment: showLeftAlignment,
icon: Icons.format_textdirection_r_to_l, showCenterAlignment: showCenterAlignment,
iconSize: toolbarIconSize, showRightAlignment: showRightAlignment,
iconTheme: iconTheme, showJustifyAlignment: showJustifyAlignment,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
), ),
if (showDividers && if (showDirection)
isButtonGroupShown[1] && ToggleStyleButton(
(isButtonGroupShown[2] || attribute: Attribute.rtl,
isButtonGroupShown[3] || tooltip: buttonTooltips[ToolbarButtons.direction],
isButtonGroupShown[4] || controller: controller,
isButtonGroupShown[5])) icon: Icons.format_textdirection_r_to_l,
QuillDivider(axis, iconSize: toolbarIconSize,
color: sectionDividerColor, space: sectionDividerSpace), iconTheme: iconTheme,
if (showHeaderStyle) afterButtonPressed: afterButtonPressed,
SelectHeaderStyleButton( ),
tooltip: buttonTooltips[ToolbarButtons.headerStyle], if (showDividers &&
controller: controller, isButtonGroupShown[1] &&
axis: axis, (isButtonGroupShown[2] ||
iconSize: toolbarIconSize, isButtonGroupShown[3] ||
iconTheme: iconTheme, isButtonGroupShown[4] ||
afterButtonPressed: afterButtonPressed, isButtonGroupShown[5]))
),
if (showDividers &&
showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showListNumbers)
ToggleStyleButton(
attribute: Attribute.ol,
tooltip: buttonTooltips[ToolbarButtons.listNumbers],
controller: controller,
icon: Icons.format_list_numbered,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showListBullets)
ToggleStyleButton(
attribute: Attribute.ul,
tooltip: buttonTooltips[ToolbarButtons.listBullets],
controller: controller,
icon: Icons.format_list_bulleted,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showListCheck)
ToggleCheckListButton(
attribute: Attribute.unchecked,
tooltip: buttonTooltips[ToolbarButtons.listChecks],
controller: controller,
icon: Icons.check_box,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showCodeBlock)
ToggleStyleButton(
attribute: Attribute.codeBlock,
tooltip: buttonTooltips[ToolbarButtons.codeBlock],
controller: controller,
icon: Icons.code,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showQuote)
ToggleStyleButton(
attribute: Attribute.blockQuote,
tooltip: buttonTooltips[ToolbarButtons.quote],
controller: controller,
icon: Icons.format_quote,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_increase,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.indentIncrease],
controller: controller,
isIncrease: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_decrease,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.indentDecrease],
controller: controller,
isIncrease: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showLink)
LinkStyleButton(
tooltip: buttonTooltips[ToolbarButtons.link],
controller: controller,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
afterButtonPressed: afterButtonPressed,
linkRegExp: linkRegExp,
linkDialogAction: linkDialogAction,
),
if (showSearchButton)
SearchButton(
icon: Icons.search,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.search],
controller: controller,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
afterButtonPressed: afterButtonPressed,
),
if (customButtons.isNotEmpty)
if (showDividers)
QuillDivider( QuillDivider(
axis, axis,
color: sectionDividerColor, color: sectionDividerColor,
space: sectionDividerSpace, space: sectionDividerSpace,
), ),
for (final customButton in customButtons) if (showHeaderStyle)
if (customButton.child != null) ...[ SelectHeaderStyleButton(
InkWell( tooltip: buttonTooltips[ToolbarButtons.headerStyle],
onTap: customButton.onTap, controller: controller,
child: customButton.child, axis: axis,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
showHeaderStyle &&
isButtonGroupShown[2] &&
(isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
if (showListNumbers)
ToggleStyleButton(
attribute: Attribute.ol,
tooltip: buttonTooltips[ToolbarButtons.listNumbers],
controller: controller,
icon: Icons.format_list_numbered,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showListBullets)
ToggleStyleButton(
attribute: Attribute.ul,
tooltip: buttonTooltips[ToolbarButtons.listBullets],
controller: controller,
icon: Icons.format_list_bulleted,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showListCheck)
ToggleCheckListButton(
attribute: Attribute.unchecked,
tooltip: buttonTooltips[ToolbarButtons.listChecks],
controller: controller,
icon: Icons.check_box,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showCodeBlock)
ToggleStyleButton(
attribute: Attribute.codeBlock,
tooltip: buttonTooltips[ToolbarButtons.codeBlock],
controller: controller,
icon: Icons.code,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
isButtonGroupShown[3] &&
(isButtonGroupShown[4] || isButtonGroupShown[5]))
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showQuote)
ToggleStyleButton(
attribute: Attribute.blockQuote,
tooltip: buttonTooltips[ToolbarButtons.quote],
controller: controller,
icon: Icons.format_quote,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_increase,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.indentIncrease],
controller: controller,
isIncrease: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showIndent)
IndentButton(
icon: Icons.format_indent_decrease,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.indentDecrease],
controller: controller,
isIncrease: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
), ),
] else ...[ if (showDividers && isButtonGroupShown[4] && isButtonGroupShown[5])
CustomButton( QuillDivider(axis,
onPressed: customButton.onTap, color: sectionDividerColor, space: sectionDividerSpace),
icon: customButton.icon, if (showLink)
iconColor: customButton.iconColor, LinkStyleButton(
tooltip: buttonTooltips[ToolbarButtons.link],
controller: controller,
iconSize: toolbarIconSize, iconSize: toolbarIconSize,
iconTheme: iconTheme, iconTheme: iconTheme,
dialogTheme: dialogTheme,
afterButtonPressed: afterButtonPressed, afterButtonPressed: afterButtonPressed,
tooltip: customButton.tooltip, linkRegExp: linkRegExp,
linkDialogAction: linkDialogAction,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
), ),
], if (showSearchButton)
], SearchButton(
icon: Icons.search,
iconSize: toolbarIconSize,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
tooltip: buttonTooltips[ToolbarButtons.search],
controller: controller,
iconTheme: iconTheme,
dialogTheme: dialogTheme,
afterButtonPressed: afterButtonPressed,
),
if (customButtons.isNotEmpty)
if (showDividers)
QuillDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
for (final customButton in customButtons)
if (customButton.child != null) ...[
InkWell(
onTap: customButton.onTap,
child: customButton.child,
),
] else ...[
CustomButton(
onPressed: customButton.onTap,
icon: customButton.icon,
iconColor: customButton.iconColor,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
tooltip: customButton.tooltip,
),
],
];
},
); );
} }
final List<Widget> children; final QuillToolbarChildrenBuilder childrenBuilder;
final Axis axis; final Axis axis;
final double toolbarSize; final double toolbarSize;
final double toolbarSectionSpacing; final double toolbarSectionSpacing;
@ -624,10 +625,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// is given. /// is given.
final Color? color; final Color? color;
// We will add this in the next major release and not now
/// The barrier color of the shown dialogs
// final Color dialogbarrierColor;
/// The locale to use for the editor toolbar, defaults to system locale /// The locale to use for the editor toolbar, defaults to system locale
/// More https://github.com/singerdmx/flutter-quill#translation /// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale; final Locale? locale;
@ -663,7 +660,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
crossAxisAlignment: toolbarIconCrossAlignment, crossAxisAlignment: toolbarIconCrossAlignment,
runSpacing: 4, runSpacing: 4,
spacing: toolbarSectionSpacing, spacing: toolbarSectionSpacing,
children: children, children: childrenBuilder(context),
) )
: Container( : Container(
decoration: decoration ?? decoration: decoration ??
@ -676,7 +673,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
), ),
child: ArrowIndicatedButtonList( child: ArrowIndicatedButtonList(
axis: axis, axis: axis,
buttons: children, buttons: childrenBuilder(context),
), ),
), ),
); );

@ -9,6 +9,10 @@ class QuillProvider extends InheritedWidget {
required super.child, required super.child,
}); });
/// Controller object which establishes a link between a rich text document
/// and this editor.
///
/// Must not be null.
final QuillConfigurations configurations; final QuillConfigurations configurations;
@override @override
@ -28,7 +32,18 @@ class QuillProvider extends InheritedWidget {
'The quill provider must be provided in the widget tree.', 'The quill provider must be provided in the widget tree.',
); );
} }
throw ArgumentError.checkNotNull(provider, 'QuillProvider'); throw ArgumentError.checkNotNull(
'You are using a widget in the Flutter quill library that require '
'The Quill provider widget to be in the parent widget tree '
'because '
'The provider is $provider. Please make sure to wrap this widget'
' with'
' QuillProvider widget. '
'You might using QuillEditor and QuillToolbar so make sure to'
' wrap them with the quill provider widget and setup the required '
'configurations',
'QuillProvider',
);
} }
return provider; return provider;
} }

@ -1,6 +1,6 @@
name: flutter_quill name: flutter_quill
description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter.
version: 7.4.16 version: 7.4.17
homepage: https://1o24bbs.com/c/bulletjournal/108 homepage: https://1o24bbs.com/c/bulletjournal/108
repository: https://github.com/singerdmx/flutter-quill repository: https://github.com/singerdmx/flutter-quill
@ -24,40 +24,8 @@ dependencies:
platform: ^3.1.3 platform: ^3.1.3
pasteboard: ^0.2.0 pasteboard: ^0.2.0
# Dependencies for testing utilities dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter
# For information on the generic Dart part of this file, see the flutter: null
# following page: https://dart.dev/tools/pub/pubspec
# The following section is specific to Flutter.
flutter: null
# To add assets to your package, add an assets section, like this:
# assets:
# - images/a_dot_burr.jpeg
# - images/a_dot_ham.jpeg
#
# For details regarding assets in packages, see
# https://flutter.dev/assets-and-images/#from-packages
#
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware.
# To add custom fonts to your package, add a fonts section here,
# in this "flutter" section. Each entry in this list should have a
# "family" key with the font family name, and a "fonts" key with a
# list giving the asset and other descriptors for the font. For
# example:
# fonts:
# - family: Schyler
# fonts:
# - asset: fonts/Schyler-Regular.ttf
# - asset: fonts/Schyler-Italic.ttf
# style: italic
# - family: Trajan Pro
# fonts:
# - asset: fonts/TrajanPro.ttf
# - asset: fonts/TrajanPro_Bold.ttf
# weight: 700
#
# For details regarding fonts in packages, see
# https://flutter.dev/custom-fonts/#from-packages

@ -18,23 +18,21 @@ void main() {
configurations: QuillConfigurations( configurations: QuillConfigurations(
controller: QuillController.basic(), controller: QuillController.basic(),
), ),
child: Builder( child: QuillToolbar.basic(
builder: (context) { showRedo: false,
return QuillToolbar.basic( customButtons: [
context: context, const QuillCustomButton(tooltip: tooltip),
showRedo: false, ],
customButtons: [const QuillCustomButton(tooltip: tooltip)],
);
},
), ),
), ),
), ),
); );
final builtinFinder = find.descendant( final builtinFinder = find.descendant(
of: find.byType(HistoryButton), of: find.byType(HistoryButton),
matching: find.byType(QuillIconButton), matching: find.byType(QuillIconButton),
matchRoot: true); matchRoot: true,
);
expect(builtinFinder, findsOneWidget); expect(builtinFinder, findsOneWidget);
final builtinButton = final builtinButton =
builtinFinder.evaluate().first.widget as QuillIconButton; builtinFinder.evaluate().first.widget as QuillIconButton;
@ -58,7 +56,9 @@ void main() {
setUp(() { setUp(() {
controller = QuillController.basic(); controller = QuillController.basic();
editor = QuillEditor.basic(controller: controller, readOnly: false); editor = QuillEditor.basic(
readOnly: false,
);
}); });
tearDown(() { tearDown(() {
@ -67,7 +67,16 @@ void main() {
testWidgets('Refocus editor after controller clears document', testWidgets('Refocus editor after controller clears document',
(tester) async { (tester) async {
await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); await tester.pumpWidget(
QuillProvider(
configurations: QuillConfigurations(controller: controller),
child: MaterialApp(
home: Column(
children: [editor],
),
),
),
);
await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
editor.focusNode.unfocus(); editor.focusNode.unfocus();
@ -80,7 +89,14 @@ void main() {
testWidgets('Refocus editor after removing block attribute', testWidgets('Refocus editor after removing block attribute',
(tester) async { (tester) async {
await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); await tester.pumpWidget(QuillProvider(
configurations: QuillConfigurations(controller: controller),
child: MaterialApp(
home: Column(
children: [editor],
),
),
));
await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
controller.formatSelection(Attribute.ul); controller.formatSelection(Attribute.ul);
@ -93,7 +109,16 @@ void main() {
}); });
testWidgets('Tap checkbox in unfocused editor', (tester) async { testWidgets('Tap checkbox in unfocused editor', (tester) async {
await tester.pumpWidget(MaterialApp(home: Column(children: [editor]))); await tester.pumpWidget(
QuillProvider(
configurations: QuillConfigurations(controller: controller),
child: MaterialApp(
home: Column(
children: [editor],
),
),
),
);
await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
controller.formatSelection(Attribute.unchecked); controller.formatSelection(Attribute.unchecked);

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_quill/flutter_quill.dart'; import 'package:flutter_quill/flutter_quill.dart';
import 'package:flutter_quill/flutter_quill_test.dart'; import 'package:flutter_quill/flutter_quill_test.dart';
import 'package:flutter_quill/src/widgets/raw_editor.dart'; import 'package:flutter_quill/src/widgets/raw_editor/raw_editor.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
void main() { void main() {
@ -22,8 +22,11 @@ void main() {
group('QuillEditor', () { group('QuillEditor', () {
testWidgets('Keyboard entered text is stored in document', (tester) async { testWidgets('Keyboard entered text is stored in document', (tester) async {
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( QuillProvider(
home: QuillEditor.basic(controller: controller, readOnly: false), configurations: QuillConfigurations(controller: controller),
child: MaterialApp(
home: QuillEditor.basic(readOnly: false),
),
), ),
); );
await tester.quillEnterText(find.byType(QuillEditor), 'test\n'); await tester.quillEnterText(find.byType(QuillEditor), 'test\n');
@ -35,20 +38,22 @@ void main() {
String? latestUri; String? latestUri;
await tester.pumpWidget( await tester.pumpWidget(
MaterialApp( MaterialApp(
home: QuillEditor( home: QuillProvider(
controller: controller, configurations: QuillConfigurations(controller: controller),
focusNode: FocusNode(), child: QuillEditor(
scrollController: ScrollController(), focusNode: FocusNode(),
scrollable: true, scrollController: ScrollController(),
padding: const EdgeInsets.all(0), scrollable: true,
autoFocus: true, padding: const EdgeInsets.all(0),
readOnly: false, autoFocus: true,
expands: true, readOnly: false,
contentInsertionConfiguration: ContentInsertionConfiguration( expands: true,
onContentInserted: (content) { contentInsertionConfiguration: ContentInsertionConfiguration(
latestUri = content.uri; onContentInserted: (content) {
}, latestUri = content.uri;
allowedMimeTypes: const <String>['image/gif'], },
allowedMimeTypes: const <String>['image/gif'],
),
), ),
), ),
), ),
@ -105,19 +110,23 @@ void main() {
} }
testWidgets('custom context menu builder', (tester) async { testWidgets('custom context menu builder', (tester) async {
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(
home: QuillEditor( MaterialApp(
controller: controller, home: QuillProvider(
focusNode: FocusNode(), configurations: QuillConfigurations(controller: controller),
scrollController: ScrollController(), child: QuillEditor(
scrollable: true, focusNode: FocusNode(),
padding: EdgeInsets.zero, scrollController: ScrollController(),
autoFocus: true, scrollable: true,
readOnly: false, padding: EdgeInsets.zero,
expands: true, autoFocus: true,
contextMenuBuilder: customBuilder, readOnly: false,
expands: true,
contextMenuBuilder: customBuilder,
),
),
), ),
)); );
// Long press to show menu // Long press to show menu
await tester.longPress(find.byType(QuillEditor)); await tester.longPress(find.byType(QuillEditor));

Loading…
Cancel
Save