From 45214576f538fb9a9c04761e1dc069d8de6f5bb6 Mon Sep 17 00:00:00 2001 From: Ahmed Hnewa <73608287+freshtechtips@users.noreply.github.com> Date: Sat, 21 Oct 2023 02:25:31 +0300 Subject: [PATCH] The first steps of the major update (#1444) --- .github/PULL_REQUEST_TEMPLATE.md | 9 +- CHANGELOG.md | 4 + README.md | 24 +- example/android/app/build.gradle | 3 +- example/lib/main.dart | 13 - example/lib/pages/home_page.dart | 301 ++++---- example/lib/pages/read_only_page.dart | 9 +- example/lib/widgets/demo_scaffold.dart | 66 +- example/macos/Podfile | 2 +- .../macos/Runner.xcodeproj/project.pbxproj | 56 +- .../xcshareddata/xcschemes/Runner.xcscheme | 178 ++--- example/pubspec.yaml | 68 +- flutter_quill_extensions/pubspec.yaml | 9 +- lib/flutter_quill.dart | 2 + lib/src/core/quill_configurations.dart | 54 ++ lib/src/models/documents/nodes/container.dart | 3 +- lib/src/models/documents/nodes/leaf.dart | 28 +- lib/src/models/documents/nodes/line.dart | 8 +- lib/src/test/widget_tester_extension.dart | 2 +- lib/src/utils/extensions/build_context.dart | 37 + lib/src/widgets/editor.dart | 28 +- .../widgets/{ => raw_editor}/raw_editor.dart | 68 +- lib/src/widgets/text_line.dart | 16 +- lib/src/widgets/toolbar.dart | 719 +++++++++--------- .../widgets/toolbar/link_style_button2.dart | 3 +- lib/src/widgets/utils/provider.dart | 50 ++ pubspec.yaml | 44 +- test/bug_fix_test.dart | 63 +- test/widgets/editor_test.dart | 67 +- 29 files changed, 1059 insertions(+), 875 deletions(-) create mode 100644 lib/src/core/quill_configurations.dart create mode 100644 lib/src/utils/extensions/build_context.dart rename lib/src/widgets/{ => raw_editor}/raw_editor.dart (98%) create mode 100644 lib/src/widgets/utils/provider.dart diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 555a1e40..d365462e 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -11,18 +11,18 @@ Closes #IssueNumber (Replace "IssueNumber" with the actual issue number you are addressing.) ## Improvements - + - Improve code readability -- Improve peformance +- Improve performance ## Features - Add a new feature -- Allow to custmize the widgets +- Allow to customize the widgets @@ -40,5 +40,6 @@ Closes #IssueNumber - [ ] I have tested these changes locally. - [ ] I have followed the code style and guidelines. - [ ] I have updated `CHANGELOG.md` with my changes in the next section -- [ ] I have run "dart format ." on the project +- [ ] I have run `dart format .`` on the project +- [ ] I have run `dart fix --apply` on the project - [ ] I have run `flutter test` and `flutter analyze` and it passed successfully diff --git a/CHANGELOG.md b/CHANGELOG.md index 7200ae2e..eeffd94f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/README.md b/README.md index 2c3cfdff..5b9c4762 100644 --- a/README.md +++ b/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... + + + + + +
+ +Made with [contrib.rocks](https://contrib.rocks). + ## Sponsors diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 9e1d2fc5..4eef2899 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -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' } diff --git a/example/lib/main.dart b/example/lib/main.dart index 1e71e94b..c4a44613 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -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: [ diff --git a/example/lib/pages/home_page.dart b/example/lib/pages/home_page.dart index 851491bb..c52369f1 100644 --- a/example/lib/pages/home_page.dart +++ b/example/lib/pages/home_page.dart @@ -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 { - QuillController? _controller; + late final QuillController _controller; + late final Future _loadDocumentFromAssetsFuture; final FocusNode _focusNode = FocusNode(); Timer? _selectAllTimer; _SelectionType _selectionType = _SelectionType.none; @@ -38,13 +39,15 @@ class _HomePageState extends State { @override void dispose() { _selectAllTimer?.cancel(); + // Dispose the controller to free resources + _controller.dispose(); super.dispose(); } @override void initState() { super.initState(); - _loadFromAssets(); + _loadDocumentFromAssetsFuture = _loadFromAssets(); } Future _loadFromAssets() async { @@ -53,67 +56,73 @@ class _HomePageState extends State { ? '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 { }); } - 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 { 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 { ); } 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 { 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: [ - 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: [ + 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 { // 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 { // 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 { // 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(); } diff --git a/example/lib/pages/read_only_page.dart b/example/lib/pages/read_only_page.dart index d0ae032e..5f04b987 100644 --- a/example/lib/pages/read_only_page.dart +++ b/example/lib/pages/read_only_page.dart @@ -26,15 +26,15 @@ class _ReadOnlyPageState extends State { 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 { ); if (kIsWeb) { quillEditor = QuillEditor( - controller: controller, scrollController: ScrollController(), scrollable: true, focusNode: _focusNode, diff --git a/example/lib/widgets/demo_scaffold.dart b/example/lib/widgets/demo_scaffold.dart index 93d38663..61cb8b10 100644 --- a/example/lib/widgets/demo_scaffold.dart +++ b/example/lib/widgets/demo_scaffold.dart @@ -84,45 +84,51 @@ class _DemoScaffoldState extends State { ); } + 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 ?? []; - 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), ); } diff --git a/example/macos/Podfile b/example/macos/Podfile index dade8dfa..0c76ccf5 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -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' diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index fa640cf1..29557646 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -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 = ""; }; + 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 = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; 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 = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 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 = ""; }; + 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 = ""; }; + 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 = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 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 = ""; }; - 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 = ""; }; /* 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 = ""; }; @@ -152,23 +152,23 @@ path = Runner; sourceTree = ""; }; - 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 = ""; }; - 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 = ""; }; /* 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; }; diff --git a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index a462e330..8cbaa660 100644 --- a/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,89 +1,89 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 33b1e268..4053c2c7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -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 \ No newline at end of file diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 7ec60cf0..83a3c069 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -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 diff --git a/lib/flutter_quill.dart b/lib/flutter_quill.dart index 22a0a20f..feee0e19 100644 --- a/lib/flutter_quill.dart +++ b/lib/flutter_quill.dart @@ -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'; diff --git a/lib/src/core/quill_configurations.dart b/lib/src/core/quill_configurations.dart new file mode 100644 index 00000000..68eef5b1 --- /dev/null +++ b/lib/src/core/quill_configurations.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; +} diff --git a/lib/src/models/documents/nodes/container.dart b/lib/src/models/documents/nodes/container.dart index c8cd3a59..f061a4ca 100644 --- a/lib/src/models/documents/nodes/container.dart +++ b/lib/src/models/documents/nodes/container.dart @@ -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 extends Node { final LinkedList _children = LinkedList(); diff --git a/lib/src/models/documents/nodes/leaf.dart b/lib/src/models/documents/nodes/leaf.dart index 8f8b13a7..556590bc 100644 --- a/lib/src/models/documents/nodes/leaf.dart +++ b/lib/src/models/documents/nodes/leaf.dart @@ -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. diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index 724425a4..b34f00fa 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -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 { @override - Leaf get defaultChild => Text(); + Leaf get defaultChild => QuillText(); @override int get length => super.length + 1; @@ -400,14 +400,14 @@ class Line extends Container { 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( diff --git a/lib/src/test/widget_tester_extension.dart b/lib/src/test/widget_tester_extension.dart index 21bb75ab..866c5c7f 100644 --- a/lib/src/test/widget_tester_extension.dart +++ b/lib/src/test/widget_tester_extension.dart @@ -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 { diff --git a/lib/src/utils/extensions/build_context.dart b/lib/src/utils/extensions/build_context.dart new file mode 100644 index 00000000..2df233ab --- /dev/null +++ b/lib/src/utils/extensions/build_context.dart @@ -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; + } +} diff --git a/lib/src/widgets/editor.dart b/lib/src/widgets/editor.dart index 690fbd5d..80c34db5 100644 --- a/lib/src/widgets/editor.dart +++ b/lib/src/widgets/editor.dart @@ -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 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; diff --git a/lib/src/widgets/raw_editor.dart b/lib/src/widgets/raw_editor/raw_editor.dart similarity index 98% rename from lib/src/widgets/raw_editor.dart rename to lib/src/widgets/raw_editor/raw_editor.dart index 71229eef..a43320dd 100644 --- a/lib/src/widgets/raw_editor.dart +++ b/lib/src/widgets/raw_editor/raw_editor.dart @@ -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(line.first); + final text = castOrNull(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(); } diff --git a/lib/src/widgets/text_line.dart b/lib/src/widgets/text_line.dart index 9358777a..6dd4f793 100644 --- a/lib/src/widgets/text_line.dart +++ b/lib/src/widgets/text_line.dart @@ -240,7 +240,7 @@ class _TextLineState extends State { TextSpan _buildTextSpan(DefaultStyles defaultStyles, LinkedList nodes, TextStyle lineStyle) { if (nodes.isEmpty && kIsWeb) { - nodes = LinkedList()..add(leaf.Text('\u{200B}')); + nodes = LinkedList()..add(leaf.QuillText('\u{200B}')); } final children = nodes .map((node) => @@ -307,7 +307,7 @@ class _TextLineState extends State { 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 { ); } - 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 { } 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; } diff --git a/lib/src/widgets/toolbar.dart b/lib/src/widgets/toolbar.dart index 2ea62fe6..ea9be6a9 100644 --- a/lib/src/widgets/toolbar.dart +++ b/lib/src/widgets/toolbar.dart @@ -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 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 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), ), ), ); diff --git a/lib/src/widgets/toolbar/link_style_button2.dart b/lib/src/widgets/toolbar/link_style_button2.dart index 9e3de80e..3655534d 100644 --- a/lib/src/widgets/toolbar/link_style_button2.dart +++ b/lib/src/widgets/toolbar/link_style_button2.dart @@ -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'; diff --git a/lib/src/widgets/utils/provider.dart b/lib/src/widgets/utils/provider.dart new file mode 100644 index 00000000..0148f2e4 --- /dev/null +++ b/lib/src/widgets/utils/provider.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(); + } + + 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; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 59faa2bb..0e1921d4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -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 \ No newline at end of file diff --git a/test/bug_fix_test.dart b/test/bug_fix_test.dart index 0689476e..ee53926c 100644 --- a/test/bug_fix_test.dart +++ b/test/bug_fix_test.dart @@ -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); diff --git a/test/widgets/editor_test.dart b/test/widgets/editor_test.dart index dccf85ea..d3453ed6 100644 --- a/test/widgets/editor_test.dart +++ b/test/widgets/editor_test.dart @@ -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 ['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 ['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));