The first steps of the major update (#1444)

pull/1448/head
Ahmed Hnewa 1 year ago committed by GitHub
parent 7c5a12b140
commit 45214576f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 9
      .github/PULL_REQUEST_TEMPLATE.md
  2. 4
      CHANGELOG.md
  3. 24
      README.md
  4. 3
      example/android/app/build.gradle
  5. 13
      example/lib/main.dart
  6. 301
      example/lib/pages/home_page.dart
  7. 9
      example/lib/pages/read_only_page.dart
  8. 66
      example/lib/widgets/demo_scaffold.dart
  9. 2
      example/macos/Podfile
  10. 56
      example/macos/Runner.xcodeproj/project.pbxproj
  11. 178
      example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme
  12. 68
      example/pubspec.yaml
  13. 9
      flutter_quill_extensions/pubspec.yaml
  14. 2
      lib/flutter_quill.dart
  15. 54
      lib/src/core/quill_configurations.dart
  16. 3
      lib/src/models/documents/nodes/container.dart
  17. 28
      lib/src/models/documents/nodes/leaf.dart
  18. 8
      lib/src/models/documents/nodes/line.dart
  19. 2
      lib/src/test/widget_tester_extension.dart
  20. 37
      lib/src/utils/extensions/build_context.dart
  21. 28
      lib/src/widgets/editor.dart
  22. 68
      lib/src/widgets/raw_editor/raw_editor.dart
  23. 16
      lib/src/widgets/text_line.dart
  24. 719
      lib/src/widgets/toolbar.dart
  25. 3
      lib/src/widgets/toolbar/link_style_button2.dart
  26. 50
      lib/src/widgets/utils/provider.dart
  27. 44
      pubspec.yaml
  28. 63
      test/bug_fix_test.dart
  29. 67
      test/widgets/editor_test.dart

@ -11,18 +11,18 @@ Closes #IssueNumber
(Replace "IssueNumber" with the actual issue number you are addressing.)
## Improvements
<!-- Please tell us the improvemenets you made in a list -->
<!-- Please tell us the improvements you made in a list -->
<!-- Example: -->
- Improve code readability
- Improve peformance
- Improve performance
## Features
<!-- Please tell us the features you added in a list if you add any -->
<!-- Example: -->
- Add a new feature
- Allow to custmize the widgets
- Allow to customize the widgets
<!-- Remove this if your pull request about other changes -->
@ -40,5 +40,6 @@ Closes #IssueNumber
- [ ] I have tested these changes locally. <!-- 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 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 -->

@ -1,3 +1,7 @@
# [7.5.0]
- **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]
- Update documentation and README.md

@ -53,21 +53,25 @@ QuillController _controller = QuillController.basic();
and then embed the toolbar and the editor, within your app. For example:
```dart
Column(
QuillProvider(
configurations: QuillConfigurations(controller: _controller),
child: Column(
children: [
QuillToolbar.basic(controller: _controller),
Expanded(
child: Container(
child: QuillEditor.basic(
controller: _controller,
readOnly: false, // true for view only mode
),
),
)
],
),
)
```
And depending on your use case, you might want to dispose the `_controller` in dispose mehtod
Check out [Sample Page] for advanced usage.
## Input / Output
@ -476,6 +480,22 @@ and then enter text using `quillEnterText`:
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
<a href="https://bulletjournal.us/home/index.html">

@ -47,7 +47,7 @@ android {
targetSdkVersion flutter.targetSdkVersion
versionCode flutterVersionCode.toInteger()
versionName flutterVersionName
multiDexEnabled true
// Multidex is not required for api level 21
}
buildTypes {
@ -64,5 +64,4 @@ flutter {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.android.support:multidex:1.0.3'
}

@ -9,26 +9,13 @@ void main() {
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Quill Demo',
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,
// 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,
),
localizationsDelegates: [

@ -11,7 +11,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_quill/extensions.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/path.dart' as path;
import 'package:path_provider/path_provider.dart';
import '../universal_ui/universal_ui.dart';
@ -30,7 +30,8 @@ class HomePage extends StatefulWidget {
}
class _HomePageState extends State<HomePage> {
QuillController? _controller;
late final QuillController _controller;
late final Future<void> _loadDocumentFromAssetsFuture;
final FocusNode _focusNode = FocusNode();
Timer? _selectAllTimer;
_SelectionType _selectionType = _SelectionType.none;
@ -38,13 +39,15 @@ class _HomePageState extends State<HomePage> {
@override
void dispose() {
_selectAllTimer?.cancel();
// Dispose the controller to free resources
_controller.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
_loadFromAssets();
_loadDocumentFromAssetsFuture = _loadFromAssets();
}
Future<void> _loadFromAssets() async {
@ -53,67 +56,73 @@ class _HomePageState extends State<HomePage> {
? 'assets/sample_data_nomedia.json'
: 'assets/sample_data.json');
final doc = Document.fromJson(jsonDecode(result));
setState(() {
_controller = QuillController(
document: doc, selection: const TextSelection.collapsed(offset: 0));
});
_controller = QuillController(
document: doc,
selection: const TextSelection.collapsed(offset: 0),
);
} catch (error) {
final doc = Document()..insert(0, 'Empty asset');
setState(() {
_controller = QuillController(
document: doc, selection: const TextSelection.collapsed(offset: 0));
});
_controller = QuillController(
document: doc,
selection: const TextSelection.collapsed(offset: 0),
);
}
}
@override
Widget build(BuildContext context) {
if (_controller == null) {
return const Scaffold(body: Center(child: Text('Loading...')));
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.grey.shade800,
elevation: 0,
centerTitle: false,
title: const Text(
'Flutter Quill',
),
actions: [
IconButton(
onPressed: () => _insertTimeStamp(
_controller!,
DateTime.now().toString(),
return FutureBuilder(
future: _loadDocumentFromAssetsFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Scaffold(
body: Center(child: CircularProgressIndicator.adaptive()),
);
}
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.grey.shade800,
elevation: 0,
centerTitle: false,
title: const Text(
'Flutter Quill',
),
icon: const Icon(Icons.add_alarm_rounded),
),
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(_controller!.document.toPlainText([
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
])),
actions: [
IconButton(
onPressed: () => _insertTimeStamp(
_controller,
DateTime.now().toString(),
),
icon: const Icon(Icons.add_alarm_rounded),
),
),
icon: const Icon(Icons.text_fields_rounded),
)
],
),
drawer: Container(
constraints:
BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width * 0.7),
color: Colors.grey.shade800,
child: _buildMenuBar(context),
),
body: _buildWelcomeEditor(context),
IconButton(
onPressed: () => showDialog(
context: context,
builder: (context) => AlertDialog(
content: Text(_controller.document.toPlainText([
...FlutterQuillEmbeds.builders(),
TimeStampEmbedBuilderWidget()
])),
),
),
icon: const Icon(Icons.text_fields_rounded),
)
],
),
drawer: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.sizeOf(context).width * 0.7),
color: Colors.grey.shade800,
child: _buildMenuBar(context),
),
body: _buildWelcomeEditor(context),
);
},
);
}
bool _onTripleClickSelection() {
final controller = _controller!;
final controller = _controller;
_selectAllTimer?.cancel();
_selectAllTimer = null;
@ -174,9 +183,40 @@ class _HomePageState extends State<HomePage> {
});
}
Widget _buildWelcomeEditor(BuildContext context) {
Widget quillEditor = QuillEditor(
controller: _controller!,
QuillEditor get quillEditor {
if (kIsWeb) {
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(),
scrollable: true,
focusNode: _focusNode,
@ -216,57 +256,11 @@ class _HomePageState extends State<HomePage> {
TimeStampEmbedBuilderWidget()
],
);
}
QuillToolbar get quillToolbar {
if (kIsWeb) {
quillEditor = QuillEditor(
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(
controller: _controller!,
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(
controller: _controller!,
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
webImagePickImpl: _webImagePickImpl,
@ -276,8 +270,7 @@ class _HomePageState extends State<HomePage> {
);
}
if (_isDesktop()) {
toolbar = QuillToolbar.basic(
controller: _controller!,
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
onImagePickCallback: _onImagePickCallback,
filePickImpl: openFileSystemPickerForDesktop,
@ -286,28 +279,78 @@ class _HomePageState extends State<HomePage> {
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(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 15,
child: Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16, right: 16),
child: quillEditor,
child: QuillProvider(
configurations: QuillConfigurations(
// (throw ArgumentError.checkNotNull(
// _controller,
// 'Quill controller',
// ))
controller: _controller,
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
Expanded(
flex: 15,
child: Container(
color: Colors.white,
padding: const EdgeInsets.only(left: 16, right: 16),
child: quillEditor,
),
),
),
kIsWeb
? Expanded(
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: toolbar,
))
: Container(child: toolbar)
],
kIsWeb
? Expanded(
child: Container(
padding:
const EdgeInsets.symmetric(vertical: 16, horizontal: 8),
child: quillToolbar,
))
: Container(child: quillToolbar)
],
),
),
);
}
@ -330,7 +373,7 @@ class _HomePageState extends State<HomePage> {
// Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile =
await file.copy('${appDocDir.path}/${basename(file.path)}');
await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString();
}
@ -355,7 +398,7 @@ class _HomePageState extends State<HomePage> {
// Copies the picked file from temporary cache to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final copiedFile =
await file.copy('${appDocDir.path}/${basename(file.path)}');
await file.copy('${appDocDir.path}/${path.basename(file.path)}');
return copiedFile.path.toString();
}
@ -453,8 +496,8 @@ class _HomePageState extends State<HomePage> {
// Saves the image to applications directory
final appDocDir = await getApplicationDocumentsDirectory();
final file = await File(
'${appDocDir.path}/${basename('${DateTime.now().millisecondsSinceEpoch}.png')}')
.writeAsBytes(imageBytes, flush: true);
'${appDocDir.path}/${path.basename('${DateTime.now().millisecondsSinceEpoch}.png')}',
).writeAsBytes(imageBytes, flush: true);
return file.path.toString();
}

@ -26,15 +26,15 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
builder: _buildContent,
showToolbar: _edit == true,
floatingActionButton: FloatingActionButton.extended(
label: Text(_edit == true ? 'Done' : 'Edit'),
onPressed: _toggleEdit,
icon: Icon(_edit == true ? Icons.check : Icons.edit)),
label: Text(_edit == true ? 'Done' : 'Edit'),
onPressed: _toggleEdit,
icon: Icon(_edit == true ? Icons.check : Icons.edit),
),
);
}
Widget _buildContent(BuildContext context, QuillController? controller) {
var quillEditor = QuillEditor(
controller: controller!,
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,
@ -46,7 +46,6 @@ class _ReadOnlyPageState extends State<ReadOnlyPage> {
);
if (kIsWeb) {
quillEditor = QuillEditor(
controller: controller,
scrollController: ScrollController(),
scrollable: true,
focusNode: _focusNode,

@ -84,45 +84,51 @@ class _DemoScaffoldState extends State<DemoScaffold> {
);
}
QuillToolbar get quillToolbar {
if (_isDesktop()) {
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop,
),
);
}
return QuillToolbar.basic(
embedButtons: FlutterQuillEmbeds.buttons(),
);
}
@override
Widget build(BuildContext context) {
if (_controller == null) {
return const Scaffold(body: Center(child: Text('Loading...')));
}
final actions = widget.actions ?? <Widget>[];
var toolbar = QuillToolbar.basic(
controller: _controller!,
embedButtons: FlutterQuillEmbeds.buttons(),
);
if (_isDesktop()) {
toolbar = QuillToolbar.basic(
controller: _controller!,
embedButtons: FlutterQuillEmbeds.buttons(
filePickImpl: openFileSystemPickerForDesktop),
);
}
return 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,
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,
actions: actions,
floatingActionButton: widget.floatingActionButton,
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,4 +1,4 @@
platform :osx, '10.11'
platform :osx, '12.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'

@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 51;
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
@ -21,12 +21,12 @@
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
07D884DE6AB8033C3F60B238 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 48A88899E2BC5FD7AFD2B040 /* Pods_Runner.framework */; };
335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; };
33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; };
33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; };
33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; };
33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; };
7C7D9700842BA0E01048B7B5 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@ -53,7 +53,7 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
0C1FD22CA47E09B485C5D2F0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
17BD0969A552CE47C17FC221 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = "<group>"; };
335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = "<group>"; };
33CC10ED2044A3C60003C045 /* app.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = app.app; sourceTree = BUILT_PRODUCTS_DIR; };
@ -68,11 +68,11 @@
33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = "<group>"; };
33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = "<group>"; };
33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = "<group>"; };
4B8510D07EAD5FFD466A09A7 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
3DB40E993F068140F6DEEA8F /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
48A88899E2BC5FD7AFD2B040 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
676737A1C184536E1D9D90A1 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = "<group>"; };
93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = "<group>"; };
F44289D50723316BB0B4ADA1 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -80,7 +80,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
7C7D9700842BA0E01048B7B5 /* Pods_Runner.framework in Frameworks */,
07D884DE6AB8033C3F60B238 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -104,8 +104,8 @@
33FAB671232836740065AC1E /* Runner */,
33CEB47122A05771004F2AC0 /* Flutter */,
33CC10EE2044A3C60003C045 /* Products */,
D73912EC22F37F3D000D13A0 /* Frameworks */,
D4C5240DF6823DEC9AB0AD3D /* Pods */,
E0CAA5D4D3AFCAEB94FF2464 /* Pods */,
C2525C9EE4B6956CB985C5A2 /* Frameworks */,
);
sourceTree = "<group>";
};
@ -152,23 +152,23 @@
path = Runner;
sourceTree = "<group>";
};
D4C5240DF6823DEC9AB0AD3D /* Pods */ = {
C2525C9EE4B6956CB985C5A2 /* Frameworks */ = {
isa = PBXGroup;
children = (
0C1FD22CA47E09B485C5D2F0 /* Pods-Runner.debug.xcconfig */,
4B8510D07EAD5FFD466A09A7 /* Pods-Runner.release.xcconfig */,
F44289D50723316BB0B4ADA1 /* Pods-Runner.profile.xcconfig */,
48A88899E2BC5FD7AFD2B040 /* Pods_Runner.framework */,
);
name = Pods;
path = Pods;
name = Frameworks;
sourceTree = "<group>";
};
D73912EC22F37F3D000D13A0 /* Frameworks */ = {
E0CAA5D4D3AFCAEB94FF2464 /* Pods */ = {
isa = PBXGroup;
children = (
93FCB7C0C7A612A19EB2D2C2 /* Pods_Runner.framework */,
3DB40E993F068140F6DEEA8F /* Pods-Runner.debug.xcconfig */,
676737A1C184536E1D9D90A1 /* Pods-Runner.release.xcconfig */,
17BD0969A552CE47C17FC221 /* Pods-Runner.profile.xcconfig */,
);
name = Frameworks;
name = Pods;
path = Pods;
sourceTree = "<group>";
};
/* End PBXGroup section */
@ -178,13 +178,13 @@
isa = PBXNativeTarget;
buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
ECE07D1381B49CCA8A8E0C5F /* [CP] Check Pods Manifest.lock */,
8E0B73589C7156B8D6C458C1 /* [CP] Check Pods Manifest.lock */,
33CC10E92044A3C60003C045 /* Sources */,
33CC10EA2044A3C60003C045 /* Frameworks */,
33CC10EB2044A3C60003C045 /* Resources */,
33CC110E2044A8840003C045 /* Bundle Framework */,
3399D490228B24CF009A79C7 /* ShellScript */,
678C02007C3A278E6325D529 /* [CP] Embed Pods Frameworks */,
64BF2FA23C00365B5E3F66C0 /* [CP] Embed Pods Frameworks */,
);
buildRules = (
);
@ -203,7 +203,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0920;
LastUpgradeCheck = 0930;
LastUpgradeCheck = 1430;
ORGANIZATIONNAME = "";
TargetAttributes = {
33CC10EC2044A3C60003C045 = {
@ -256,6 +256,7 @@
/* Begin PBXShellScriptBuildPhase section */
3399D490228B24CF009A79C7 /* ShellScript */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
@ -291,7 +292,7 @@
shellPath = /bin/sh;
shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire";
};
678C02007C3A278E6325D529 /* [CP] Embed Pods Frameworks */ = {
64BF2FA23C00365B5E3F66C0 /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -308,7 +309,7 @@
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
ECE07D1381B49CCA8A8E0C5F /* [CP] Check Pods Manifest.lock */ = {
8E0B73589C7156B8D6C458C1 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
@ -404,7 +405,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@ -426,6 +427,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};
@ -483,7 +485,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = macosx;
@ -530,7 +532,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
MACOSX_DEPLOYMENT_TARGET = 10.11;
MACOSX_DEPLOYMENT_TARGET = "$(RECOMMENDED_MACOSX_DEPLOYMENT_TARGET)";
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = macosx;
SWIFT_COMPILATION_MODE = wholemodule;
@ -552,6 +554,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@ -572,6 +575,7 @@
"$(inherited)",
"@executable_path/../Frameworks",
);
MACOSX_DEPLOYMENT_TARGET = 12.0;
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_VERSION = 5.0;
};

@ -1,89 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1000"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1430"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Profile"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "33CC10EC2044A3C60003C045"
BuildableName = "app.app"
BlueprintName = "Runner"
ReferencedContainer = "container:Runner.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

@ -1,20 +1,6 @@
name: app
description: demo app
# 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
publish_to: 'none'
version: 1.0.0+1
environment:
@ -23,14 +9,12 @@ environment:
dependencies:
flutter:
sdk: flutter
universal_html: ^2.0.8
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.4
path_provider: ^2.0.9
filesystem_picker: ^3.1.0
file_picker: ^5.2.2
cupertino_icons: ^1.0.6
path_provider: ^2.1.1
filesystem_picker: ^4.0.0
file_picker: ^6.0.0
flutter_quill:
path: ../
flutter_quill_extensions:
@ -44,21 +28,9 @@ dev_dependencies:
flutter_test:
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:
# 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
# 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/
@ -89,30 +61,4 @@ flutter:
- asset: assets/fonts/RobotoMono-Regular.ttf
- family: SF-UI-Display
fonts:
- 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
- asset: assets/fonts/SF-Pro-Display-Regular.otf

@ -3,6 +3,13 @@ description: Embed extensions for flutter_quill including image, video, formula
version: 0.5.1
homepage: https://bulletjournal.us/home/index.html
repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions
platforms:
android:
ios:
# linux:
macos:
web:
windows:
environment:
sdk: ">=2.17.0 <4.0.0"
@ -12,7 +19,7 @@ dependencies:
flutter:
sdk: flutter
flutter_quill: ^7.4.15
flutter_quill: ^7.4.16
# In case you are working on changes for both libraries,
# flutter_quill:
# path: ~/development/playground/framework_based/flutter/flutter-quill

@ -1,5 +1,6 @@
library flutter_quill;
export 'src/core/quill_configurations.dart';
export 'src/models/documents/attribute.dart';
export 'src/models/documents/document.dart';
export 'src/models/documents/nodes/block.dart';
@ -27,3 +28,4 @@ export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction;
export 'src/widgets/style_widgets/style_widgets.dart';
export 'src/widgets/toolbar.dart';
export 'src/widgets/toolbar/enum.dart';
export 'src/widgets/utils/provider.dart';

@ -0,0 +1,54 @@
import 'package:flutter/foundation.dart' show immutable;
import 'package:flutter/material.dart' show Color, Colors;
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
class QuillConfigurations {
const QuillConfigurations({
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 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
/// 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 {
final LinkedList<Node> _children = LinkedList<Node>();

@ -16,12 +16,12 @@ abstract class Leaf extends Node {
}
final text = data as String;
assert(text.isNotEmpty);
return Text(text);
return QuillText(text);
}
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].
Object get value => _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.
var node = this as Text;
var node = this as QuillText;
// Merging it with previous node if style is the same.
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;
node.unlink();
node = prev;
@ -131,7 +131,7 @@ abstract class Leaf extends Node {
// Merging it with next node if style is the same.
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;
next.unlink();
}
@ -157,7 +157,7 @@ abstract class Leaf extends Node {
return isLast ? null : next as Leaf?;
}
assert(this is Text);
assert(this is QuillText);
final text = _value as String;
_value = text.substring(0, index);
final split = Leaf(text.substring(index))..applyStyle(style);
@ -200,6 +200,9 @@ abstract class Leaf extends Node {
}
}
@Deprecated('Please use [QuillText] instead')
class Text extends QuillText {}
/// A span of formatted text within a line in a Quill document.
///
/// Text is a leaf node of a document tree.
@ -211,13 +214,18 @@ abstract class Leaf extends Node {
///
/// * [Embed], a leaf node representing an embeddable object.
/// * [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')),
super.val(text);
@override
Node newInstance() => Text(value);
Node newInstance() => QuillText(value);
@override
String get value => _value as String;
@ -232,7 +240,7 @@ class Text extends Leaf {
/// 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,
/// horizontal rule, video, or any other object with defined structure,
/// like a tweet, for instance.

@ -15,13 +15,13 @@ import 'node.dart';
/// 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
/// or text nodes are allowed.
class Line extends Container<Leaf?> {
@override
Leaf get defaultChild => Text();
Leaf get defaultChild => QuillText();
@override
int get length => super.length + 1;
@ -400,14 +400,14 @@ class Line extends Container<Leaf?> {
if (node != null) {
var pos = 0;
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));
} else if (node.value is Embeddable) {
result.add(OffsetValue(beg, node.value as Embeddable, node.length));
}
while (!node!.isLast && pos < local) {
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));
} else if (node.value is Embeddable) {
result.add(

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

@ -0,0 +1,37 @@
import 'package:flutter/widgets.dart' show BuildContext;
import '../../../flutter_quill.dart';
extension BuildContextExt on BuildContext {
QuillProvider get requireQuillProvider {
return QuillProvider.ofNotNull(this);
}
QuillProvider? get quillProvider {
return QuillProvider.of(this);
}
QuillController? get quilController {
return quillProvider?.configurations.controller;
}
QuillController get requireQuillController {
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: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/leaf.dart';
import '../models/structs/offset_value.dart';
import '../models/themes/quill_dialog_theme.dart';
import '../utils/extensions/build_context.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';
import 'raw_editor/raw_editor.dart';
import 'text_selection.dart';
/// Base interface for the editor state which defines contract used by
@ -148,7 +143,6 @@ abstract class RenderAbstractEditor implements TextLayoutMetrics {
class QuillEditor extends StatefulWidget {
const QuillEditor({
required this.controller,
required this.focusNode,
required this.scrollController,
required this.scrollable,
@ -198,7 +192,6 @@ class QuillEditor extends StatefulWidget {
}) : super(key: key);
factory QuillEditor.basic({
required QuillController controller,
required bool readOnly,
TextSelectionThemeData? textSelectionThemeData,
Brightness? keyboardAppearance,
@ -215,7 +208,6 @@ class QuillEditor extends StatefulWidget {
Locale? locale,
}) {
return QuillEditor(
controller: controller,
scrollController: ScrollController(),
scrollable: true,
focusNode: focusNode ?? FocusNode(),
@ -232,11 +224,7 @@ class QuillEditor extends StatefulWidget {
);
}
/// Controller object which establishes a link between a rich text document
/// and this editor.
///
/// Must not be null.
final QuillController controller;
// final QuillController controller;
/// Controls whether this editor has keyboard focus.
final FocusNode focusNode;
@ -523,7 +511,7 @@ class QuillEditorState extends State<QuillEditor>
final child = RawEditor(
key: _editorKey,
controller: widget.controller,
controller: context.requireQuillController,
focusNode: widget.focusNode,
scrollController: widget.scrollController,
scrollable: widget.scrollable,
@ -692,7 +680,7 @@ class _QuillEditorSelectionGestureDetectorBuilder
}
bool _isPositionSelected(TapUpDetails details) {
if (_state.widget.controller.document.isEmpty()) {
if (_state.context.requireQuillController.document.isEmpty()) {
return false;
}
final pos = renderEditor!.getPositionForOffset(details.globalPosition);
@ -705,7 +693,9 @@ class _QuillEditorSelectionGestureDetectorBuilder
final segmentLeaf = result.leaf;
if (segmentLeaf == null && line.length == 1) {
editor!.widget.controller.updateSelection(
TextSelection.collapsed(offset: pos.offset), ChangeSource.LOCAL);
TextSelection.collapsed(offset: pos.offset),
ChangeSource.LOCAL,
);
return true;
}
return false;

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

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

@ -1,29 +1,10 @@
import 'package:flutter/material.dart';
import 'package:i18n_extension/i18n_widget.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 '../../flutter_quill.dart';
import '../translations/toolbar.i18n.dart';
import 'controller.dart';
import 'embeds.dart';
import '../utils/extensions/build_context.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/color_button.dart';
@ -50,9 +31,13 @@ const double kIconButtonFactor = 1.77;
/// The horizontal margin between the contents of each toolbar section.
const double kToolbarSectionSpacing = 4;
typedef QuillToolbarChildrenBuilder = List<Widget> Function(
BuildContext context,
);
class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
const QuillToolbar({
required this.children,
required this.childrenBuilder,
this.axis = Axis.horizontal,
this.toolbarSize = kDefaultIconSize * 2,
this.toolbarSectionSpacing = kToolbarSectionSpacing,
@ -71,7 +56,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
}) : super(key: key);
factory QuillToolbar.basic({
required QuillController controller,
Axis axis = Axis.horizontal,
double toolbarIconSize = kDefaultIconSize,
double toolbarSectionSpacing = kToolbarSectionSpacing,
@ -261,349 +245,370 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
customButtons: customButtons,
locale: locale,
afterButtonPressed: afterButtonPressed,
children: [
if (showUndo)
HistoryButton(
icon: Icons.undo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.undo],
controller: controller,
undo: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showRedo)
HistoryButton(
icon: Icons.redo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.redo],
controller: controller,
undo: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showFontFamily)
QuillFontFamilyButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.fontFamily],
attribute: Attribute.font,
controller: controller,
rawItemsMap: fontFamilies,
afterButtonPressed: afterButtonPressed,
),
if (showFontSize)
QuillFontSizeButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.fontSize],
attribute: Attribute.size,
controller: controller,
rawItemsMap: fontSizes,
afterButtonPressed: afterButtonPressed,
),
if (showBoldButton)
ToggleStyleButton(
attribute: Attribute.bold,
icon: Icons.format_bold,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.bold],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSubscript)
ToggleStyleButton(
attribute: Attribute.subscript,
icon: Icons.subscript,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.subscript],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSuperscript)
ToggleStyleButton(
attribute: Attribute.superscript,
icon: Icons.superscript,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.superscript],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showItalicButton)
ToggleStyleButton(
attribute: Attribute.italic,
icon: Icons.format_italic,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.italic],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSmallButton)
ToggleStyleButton(
attribute: Attribute.small,
icon: Icons.format_size,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.small],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showUnderLineButton)
ToggleStyleButton(
attribute: Attribute.underline,
icon: Icons.format_underline,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.underline],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showStrikeThrough)
ToggleStyleButton(
attribute: Attribute.strikeThrough,
icon: Icons.format_strikethrough,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.strikeThrough],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showInlineCode)
ToggleStyleButton(
attribute: Attribute.inlineCode,
icon: Icons.code,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.inlineCode],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showColorButton)
ColorButton(
icon: Icons.color_lens,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.color],
controller: controller,
background: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showBackgroundColorButton)
ColorButton(
icon: Icons.format_color_fill,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.backgroundColor],
controller: controller,
background: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showClearFormat)
ClearFormatButton(
icon: Icons.format_clear,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.clearFormat],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (embedButtons != null)
for (final builder in embedButtons)
builder(controller, toolbarIconSize, iconTheme, dialogTheme),
if (showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showAlignmentButtons)
SelectAlignmentButton(
controller: controller,
tooltips: Map.of(buttonTooltips)
..removeWhere((key, value) => ![
ToolbarButtons.leftAlignment,
ToolbarButtons.centerAlignment,
ToolbarButtons.rightAlignment,
ToolbarButtons.justifyAlignment,
].contains(key)),
iconSize: toolbarIconSize,
iconTheme: iconTheme,
showLeftAlignment: showLeftAlignment,
showCenterAlignment: showCenterAlignment,
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
afterButtonPressed: afterButtonPressed,
),
if (showDirection)
ToggleStyleButton(
attribute: Attribute.rtl,
tooltip: buttonTooltips[ToolbarButtons.direction],
controller: controller,
icon: Icons.format_textdirection_r_to_l,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(axis,
color: sectionDividerColor, space: sectionDividerSpace),
if (showHeaderStyle)
SelectHeaderStyleButton(
tooltip: buttonTooltips[ToolbarButtons.headerStyle],
controller: controller,
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,
),
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)
childrenBuilder: (context) {
final controller = context.requireQuillController;
return [
if (showUndo)
HistoryButton(
icon: Icons.undo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.undo],
controller: controller,
undo: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showRedo)
HistoryButton(
icon: Icons.redo_outlined,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.redo],
controller: controller,
undo: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showFontFamily)
QuillFontFamilyButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.fontFamily],
attribute: Attribute.font,
controller: controller,
rawItemsMap: fontFamilies,
afterButtonPressed: afterButtonPressed,
),
if (showFontSize)
QuillFontSizeButton(
iconTheme: iconTheme,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.fontSize],
attribute: Attribute.size,
controller: controller,
rawItemsMap: fontSizes,
afterButtonPressed: afterButtonPressed,
),
if (showBoldButton)
ToggleStyleButton(
attribute: Attribute.bold,
icon: Icons.format_bold,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.bold],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSubscript)
ToggleStyleButton(
attribute: Attribute.subscript,
icon: Icons.subscript,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.subscript],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSuperscript)
ToggleStyleButton(
attribute: Attribute.superscript,
icon: Icons.superscript,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.superscript],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showItalicButton)
ToggleStyleButton(
attribute: Attribute.italic,
icon: Icons.format_italic,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.italic],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showSmallButton)
ToggleStyleButton(
attribute: Attribute.small,
icon: Icons.format_size,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.small],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showUnderLineButton)
ToggleStyleButton(
attribute: Attribute.underline,
icon: Icons.format_underline,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.underline],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showStrikeThrough)
ToggleStyleButton(
attribute: Attribute.strikeThrough,
icon: Icons.format_strikethrough,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.strikeThrough],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showInlineCode)
ToggleStyleButton(
attribute: Attribute.inlineCode,
icon: Icons.code,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.inlineCode],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showColorButton)
ColorButton(
icon: Icons.color_lens,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.color],
controller: controller,
background: false,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
),
if (showBackgroundColorButton)
ColorButton(
icon: Icons.format_color_fill,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.backgroundColor],
controller: controller,
background: true,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
dialogBarrierColor:
context.requireSharedQuillConfigurations.dialogBarrierColor,
),
if (showClearFormat)
ClearFormatButton(
icon: Icons.format_clear,
iconSize: toolbarIconSize,
tooltip: buttonTooltips[ToolbarButtons.clearFormat],
controller: controller,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (embedButtons != null)
for (final builder in embedButtons)
builder(controller, toolbarIconSize, iconTheme, dialogTheme),
if (showDividers &&
isButtonGroupShown[0] &&
(isButtonGroupShown[1] ||
isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
if (showAlignmentButtons)
SelectAlignmentButton(
controller: controller,
tooltips: Map.of(buttonTooltips)
..removeWhere((key, value) => ![
ToolbarButtons.leftAlignment,
ToolbarButtons.centerAlignment,
ToolbarButtons.rightAlignment,
ToolbarButtons.justifyAlignment,
].contains(key)),
iconSize: toolbarIconSize,
iconTheme: iconTheme,
showLeftAlignment: showLeftAlignment,
showCenterAlignment: showCenterAlignment,
showRightAlignment: showRightAlignment,
showJustifyAlignment: showJustifyAlignment,
afterButtonPressed: afterButtonPressed,
),
if (showDirection)
ToggleStyleButton(
attribute: Attribute.rtl,
tooltip: buttonTooltips[ToolbarButtons.direction],
controller: controller,
icon: Icons.format_textdirection_r_to_l,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
),
if (showDividers &&
isButtonGroupShown[1] &&
(isButtonGroupShown[2] ||
isButtonGroupShown[3] ||
isButtonGroupShown[4] ||
isButtonGroupShown[5]))
QuillDivider(
axis,
color: sectionDividerColor,
space: sectionDividerSpace,
),
for (final customButton in customButtons)
if (customButton.child != null) ...[
InkWell(
onTap: customButton.onTap,
child: customButton.child,
if (showHeaderStyle)
SelectHeaderStyleButton(
tooltip: buttonTooltips[ToolbarButtons.headerStyle],
controller: controller,
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,
),
] else ...[
CustomButton(
onPressed: customButton.onTap,
icon: customButton.icon,
iconColor: customButton.iconColor,
if (showListNumbers)
ToggleStyleButton(
attribute: Attribute.ol,
tooltip: buttonTooltips[ToolbarButtons.listNumbers],
controller: controller,
icon: Icons.format_list_numbered,
iconSize: toolbarIconSize,
iconTheme: iconTheme,
afterButtonPressed: afterButtonPressed,
tooltip: customButton.tooltip,
),
],
],
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,
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 double toolbarSize;
final double toolbarSectionSpacing;
@ -620,10 +625,6 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
/// is given.
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
/// More https://github.com/singerdmx/flutter-quill#translation
final Locale? locale;
@ -659,7 +660,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
crossAxisAlignment: toolbarIconCrossAlignment,
runSpacing: 4,
spacing: toolbarSectionSpacing,
children: children,
children: childrenBuilder(context),
)
: Container(
decoration: decoration ??
@ -672,7 +673,7 @@ class QuillToolbar extends StatelessWidget implements PreferredSizeWidget {
),
child: ArrowIndicatedButtonList(
axis: axis,
buttons: children,
buttons: childrenBuilder(context),
),
),
);

@ -2,7 +2,8 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:url_launcher/link.dart';
import '../../../extensions.dart';
import '../../../extensions.dart'
show UtilityWidgets, AutoFormatMultipleLinksRule;
import '../../../translations.dart';
import '../../models/documents/attribute.dart';
import '../../models/themes/quill_dialog_theme.dart';

@ -0,0 +1,50 @@
import 'package:flutter/foundation.dart' show debugPrint, kDebugMode;
import 'package:flutter/widgets.dart' show InheritedWidget, BuildContext;
import '../../core/quill_configurations.dart';
class QuillProvider extends InheritedWidget {
const QuillProvider({
required this.configurations,
required super.child,
});
/// Controller object which establishes a link between a rich text document
/// and this editor.
///
/// Must not be null.
final QuillConfigurations configurations;
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
throw false;
}
static QuillProvider? of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<QuillProvider>();
}
static QuillProvider ofNotNull(BuildContext context) {
final provider = of(context);
if (provider == null) {
if (kDebugMode) {
debugPrint(
'The quill provider must be provided in the widget tree.',
);
}
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;
}
}

@ -1,8 +1,15 @@
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.
version: 7.4.16
version: 7.5.0
homepage: https://1o24bbs.com/c/bulletjournal/108
repository: https://github.com/singerdmx/flutter-quill
platforms:
android:
ios:
linux:
macos:
web:
windows:
environment:
sdk: ">=2.17.0 <4.0.0"
@ -24,40 +31,7 @@ dependencies:
platform: ^3.1.3
pasteboard: ^0.2.0
# Dependencies for testing utilities
flutter_test:
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: 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
flutter: null

@ -12,17 +12,27 @@ void main() {
(tester) async {
const tooltip = 'custom button';
await tester.pumpWidget(MaterialApp(
home: QuillToolbar.basic(
showRedo: false,
controller: QuillController.basic(),
customButtons: [const QuillCustomButton(tooltip: tooltip)],
)));
await tester.pumpWidget(
MaterialApp(
home: QuillProvider(
configurations: QuillConfigurations(
controller: QuillController.basic(),
),
child: QuillToolbar.basic(
showRedo: false,
customButtons: [
const QuillCustomButton(tooltip: tooltip),
],
),
),
),
);
final builtinFinder = find.descendant(
of: find.byType(HistoryButton),
matching: find.byType(QuillIconButton),
matchRoot: true);
of: find.byType(HistoryButton),
matching: find.byType(QuillIconButton),
matchRoot: true,
);
expect(builtinFinder, findsOneWidget);
final builtinButton =
builtinFinder.evaluate().first.widget as QuillIconButton;
@ -46,7 +56,9 @@ void main() {
setUp(() {
controller = QuillController.basic();
editor = QuillEditor.basic(controller: controller, readOnly: false);
editor = QuillEditor.basic(
readOnly: false,
);
});
tearDown(() {
@ -55,7 +67,16 @@ void main() {
testWidgets('Refocus editor after controller clears document',
(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');
editor.focusNode.unfocus();
@ -68,7 +89,14 @@ void main() {
testWidgets('Refocus editor after removing block attribute',
(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');
controller.formatSelection(Attribute.ul);
@ -81,7 +109,16 @@ void main() {
});
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');
controller.formatSelection(Attribute.unchecked);

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

Loading…
Cancel
Save