├── FETCH_HEAD ├── example ├── linux │ ├── .gitignore │ ├── main.cc │ ├── flutter │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugins.cmake │ │ ├── generated_plugin_registrant.cc │ │ └── CMakeLists.txt │ └── my_application.h ├── ios │ ├── Runner │ │ ├── Runner-Bridging-Header.h │ │ ├── AppDelegate.h │ │ ├── Assets.xcassets │ │ │ ├── LaunchImage.imageset │ │ │ │ ├── LaunchImage.png │ │ │ │ ├── LaunchImage@2x.png │ │ │ │ ├── LaunchImage@3x.png │ │ │ │ ├── README.md │ │ │ │ └── Contents.json │ │ │ └── AppIcon.appiconset │ │ │ │ ├── Icon-App-20x20@1x.png │ │ │ │ ├── Icon-App-20x20@2x.png │ │ │ │ ├── Icon-App-20x20@3x.png │ │ │ │ ├── Icon-App-29x29@1x.png │ │ │ │ ├── Icon-App-29x29@2x.png │ │ │ │ ├── Icon-App-29x29@3x.png │ │ │ │ ├── Icon-App-40x40@1x.png │ │ │ │ ├── Icon-App-40x40@2x.png │ │ │ │ ├── Icon-App-40x40@3x.png │ │ │ │ ├── Icon-App-60x60@2x.png │ │ │ │ ├── Icon-App-60x60@3x.png │ │ │ │ ├── Icon-App-76x76@1x.png │ │ │ │ ├── Icon-App-76x76@2x.png │ │ │ │ ├── Icon-App-1024x1024@1x.png │ │ │ │ ├── Icon-App-83.5x83.5@2x.png │ │ │ │ └── Contents.json │ │ ├── main.m │ │ ├── AppDelegate.swift │ │ ├── AppDelegate.m │ │ ├── Base.lproj │ │ │ ├── Main.storyboard │ │ │ └── LaunchScreen.storyboard │ │ └── Info.plist │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ ├── contents.xcworkspacedata │ │ │ └── xcshareddata │ │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ ├── WorkspaceSettings.xcsettings │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ └── Podfile ├── web │ ├── favicon.png │ ├── icons │ │ ├── Icon-192.png │ │ └── Icon-512.png │ ├── manifest.json │ └── index.html ├── assets │ └── fonts │ │ ├── Serif.ttf │ │ ├── MonoSpace.ttf │ │ ├── SansSerif.ttf │ │ ├── Nunito-Regular.ttf │ │ ├── Pacifico-Regular.ttf │ │ ├── RobotoMono-Regular.ttf │ │ ├── SquarePeg-Regular.ttf │ │ ├── IbarraRealNova-Regular.ttf │ │ └── SF-Pro-Display-Regular.otf ├── android │ ├── gradle.properties │ ├── app │ │ ├── src │ │ │ ├── main │ │ │ │ ├── res │ │ │ │ │ ├── mipmap-hdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-mdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ │ │ └── ic_launcher.png │ │ │ │ │ ├── drawable │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── drawable-v21 │ │ │ │ │ │ └── launch_background.xml │ │ │ │ │ ├── values │ │ │ │ │ │ └── styles.xml │ │ │ │ │ └── values-night │ │ │ │ │ │ └── styles.xml │ │ │ │ ├── kotlin │ │ │ │ │ └── com │ │ │ │ │ │ └── example │ │ │ │ │ │ └── example │ │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── AndroidManifest.xml │ │ │ ├── debug │ │ │ │ └── AndroidManifest.xml │ │ │ └── profile │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── gradle │ │ └── wrapper │ │ │ └── gradle-wrapper.properties │ ├── .gitignore │ ├── settings.gradle │ └── build.gradle ├── macos │ ├── Runner │ │ ├── Configs │ │ │ ├── Debug.xcconfig │ │ │ ├── Release.xcconfig │ │ │ ├── Warnings.xcconfig │ │ │ └── AppInfo.xcconfig │ │ ├── Assets.xcassets │ │ │ └── AppIcon.appiconset │ │ │ │ ├── app_icon_1024.png │ │ │ │ ├── app_icon_128.png │ │ │ │ ├── app_icon_16.png │ │ │ │ ├── app_icon_256.png │ │ │ │ ├── app_icon_32.png │ │ │ │ ├── app_icon_512.png │ │ │ │ ├── app_icon_64.png │ │ │ │ └── Contents.json │ │ ├── AppDelegate.swift │ │ ├── Release.entitlements │ │ ├── MainFlutterWindow.swift │ │ ├── DebugProfile.entitlements │ │ └── Info.plist │ ├── .gitignore │ ├── Flutter │ │ ├── Flutter-Debug.xcconfig │ │ ├── Flutter-Release.xcconfig │ │ └── GeneratedPluginRegistrant.swift │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── xcshareddata │ │ │ │ └── IDEWorkspaceChecks.plist │ │ └── xcshareddata │ │ │ └── xcschemes │ │ │ └── Runner.xcscheme │ └── Podfile ├── lib │ ├── universal_ui │ │ ├── fake_ui.dart │ │ ├── real_ui.dart │ │ └── universal_ui.dart │ ├── main.dart │ ├── widgets │ │ ├── time_stamp_embed_widget.dart │ │ └── responsive_widget.dart │ └── pages │ │ └── read_only_page.dart ├── windows │ ├── runner │ │ ├── resources │ │ │ └── app_icon.ico │ │ ├── resource.h │ │ ├── CMakeLists.txt │ │ ├── utils.h │ │ ├── runner.exe.manifest │ │ ├── run_loop.h │ │ ├── flutter_window.h │ │ ├── main.cpp │ │ ├── utils.cpp │ │ ├── flutter_window.cpp │ │ ├── run_loop.cpp │ │ ├── Runner.rc │ │ └── win32_window.h │ ├── .gitignore │ ├── flutter │ │ ├── generated_plugin_registrant.h │ │ ├── generated_plugin_registrant.cc │ │ └── generated_plugins.cmake │ └── CMakeLists.txt ├── .metadata ├── README.md ├── .gitignore ├── test │ └── widget_test.dart └── pubspec.yaml ├── lib ├── src │ ├── utils │ │ ├── cast.dart │ │ ├── experimental.dart │ │ ├── font.dart │ │ ├── extensions │ │ │ └── quill_controller.dart │ │ ├── embeds.dart │ │ ├── widgets.dart │ │ ├── platform.dart │ │ ├── string.dart │ │ └── delta.dart │ ├── widgets │ │ ├── style_widgets │ │ │ ├── style_widgets.dart │ │ │ ├── bullet_point.dart │ │ │ ├── checkbox_point.dart │ │ │ └── number_point.dart │ │ ├── float_cursor.dart │ │ ├── embeds.dart │ │ ├── toolbar │ │ │ └── buttons │ │ │ │ ├── custom_button.dart │ │ │ │ └── quill_icon.dart │ │ └── keyboard_listener.dart │ ├── models │ │ ├── structs │ │ │ ├── offset_value.dart │ │ │ ├── image_url.dart │ │ │ ├── history_changed.dart │ │ │ ├── vertical_spacing.dart │ │ │ ├── link_dialog_action.dart │ │ │ ├── segment_leaf_node.dart │ │ │ ├── doc_change.dart │ │ │ └── optional_size.dart │ │ ├── config │ │ │ ├── editor │ │ │ │ ├── element_options.dart │ │ │ │ └── elements │ │ │ │ │ └── code_block.dart │ │ │ ├── toolbar │ │ │ │ ├── buttons │ │ │ │ │ ├── indent.dart │ │ │ │ │ ├── clear_format.dart │ │ │ │ │ ├── toggle_style.dart │ │ │ │ │ ├── history.dart │ │ │ │ │ ├── link_style.dart │ │ │ │ │ ├── select_header_style.dart │ │ │ │ │ ├── color.dart │ │ │ │ │ ├── toggle_check_list.dart │ │ │ │ │ ├── search.dart │ │ │ │ │ └── select_alignment.dart │ │ │ │ └── base_configurations.dart │ │ │ ├── others │ │ │ │ └── animations.dart │ │ │ ├── quill_configurations.dart │ │ │ └── shared_configurations.dart │ │ ├── themes │ │ │ ├── quill_custom_button.dart │ │ │ └── quill_icon_theme.dart │ │ ├── documents │ │ │ └── nodes │ │ │ │ ├── embeddable.dart │ │ │ │ └── block.dart │ │ └── rules │ │ │ └── rule.dart │ └── test │ │ └── widget_tester_extension.dart ├── quill_delta.dart ├── flutter_quill_test.dart ├── translations.dart ├── extensions.dart └── flutter_quill.dart ├── flutter_quill_extensions ├── lib │ ├── shims │ │ ├── dart_ui_real.dart │ │ └── dart_ui_fake.dart │ └── embeds │ │ ├── toolbar │ │ ├── formula_button.dart │ │ └── video_button.dart │ │ ├── embed_types.dart │ │ ├── widgets │ │ ├── youtube_video_app.dart │ │ └── image_resizer.dart │ │ └── utils.dart ├── .metadata ├── README.md ├── pubspec.yaml ├── LICENSE ├── analysis_options.yaml └── CHANGELOG.md ├── .metadata ├── before-push.sh ├── .github ├── ISSUE_TEMPLATE │ └── issue-template.md ├── workflows │ └── main.yml └── PULL_REQUEST_TEMPLATE.md ├── pubspec.yaml ├── LICENSE ├── analysis_options.yaml └── .gitignore /FETCH_HEAD: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/linux/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral 2 | -------------------------------------------------------------------------------- /lib/src/utils/cast.dart: -------------------------------------------------------------------------------- 1 | T? castOrNull(dynamic x) => x is T ? x : null; 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" 2 | -------------------------------------------------------------------------------- /example/web/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/web/favicon.png -------------------------------------------------------------------------------- /lib/quill_delta.dart: -------------------------------------------------------------------------------- 1 | library flutter_quill.delta; 2 | 3 | export 'src/models/quill_delta.dart'; 4 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/shims/dart_ui_real.dart: -------------------------------------------------------------------------------- 1 | export 'dart:ui' if (dart.library.html) 'dart:ui_web'; 2 | -------------------------------------------------------------------------------- /example/assets/fonts/Serif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/Serif.ttf -------------------------------------------------------------------------------- /example/web/icons/Icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/web/icons/Icon-192.png -------------------------------------------------------------------------------- /example/web/icons/Icon-512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/web/icons/Icon-512.png -------------------------------------------------------------------------------- /lib/flutter_quill_test.dart: -------------------------------------------------------------------------------- 1 | library flutter_quill_test; 2 | 3 | export 'src/test/widget_tester_extension.dart'; 4 | -------------------------------------------------------------------------------- /lib/translations.dart: -------------------------------------------------------------------------------- 1 | library flutter_quill.translations; 2 | 3 | export 'src/translations/toolbar.i18n.dart'; 4 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | android.useAndroidX=true 3 | android.enableJetifier=true 4 | -------------------------------------------------------------------------------- /example/assets/fonts/MonoSpace.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/MonoSpace.ttf -------------------------------------------------------------------------------- /example/assets/fonts/SansSerif.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/SansSerif.ttf -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Debug.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/assets/fonts/Nunito-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/Nunito-Regular.ttf -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "../../Flutter/Flutter-Release.xcconfig" 2 | #include "Warnings.xcconfig" 3 | -------------------------------------------------------------------------------- /example/assets/fonts/Pacifico-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/Pacifico-Regular.ttf -------------------------------------------------------------------------------- /example/assets/fonts/RobotoMono-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/RobotoMono-Regular.ttf -------------------------------------------------------------------------------- /example/assets/fonts/SquarePeg-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/SquarePeg-Regular.ttf -------------------------------------------------------------------------------- /example/lib/universal_ui/fake_ui.dart: -------------------------------------------------------------------------------- 1 | class PlatformViewRegistry { 2 | static void registerViewFactory(String viewId, dynamic cb) {} 3 | } 4 | -------------------------------------------------------------------------------- /example/macos/.gitignore: -------------------------------------------------------------------------------- 1 | # Flutter-related 2 | **/Flutter/ephemeral/ 3 | **/Pods/ 4 | 5 | # Xcode-related 6 | **/xcuserdata/ 7 | Podfile.lock -------------------------------------------------------------------------------- /example/windows/runner/resources/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/windows/runner/resources/app_icon.ico -------------------------------------------------------------------------------- /lib/src/widgets/style_widgets/style_widgets.dart: -------------------------------------------------------------------------------- 1 | export 'bullet_point.dart'; 2 | export 'checkbox_point.dart'; 3 | export 'number_point.dart'; 4 | -------------------------------------------------------------------------------- /example/assets/fonts/IbarraRealNova-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/IbarraRealNova-Regular.ttf -------------------------------------------------------------------------------- /example/assets/fonts/SF-Pro-Display-Regular.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/assets/fonts/SF-Pro-Display-Regular.otf -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.h: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | @interface AppDelegate : FlutterAppDelegate 5 | 6 | @end 7 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /example/macos/Flutter/Flutter-Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "ephemeral/Flutter-Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_128.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_16.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_32.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_64.png -------------------------------------------------------------------------------- /lib/src/models/structs/offset_value.dart: -------------------------------------------------------------------------------- 1 | class OffsetValue { 2 | OffsetValue(this.offset, this.value, [this.length]); 3 | final int offset; 4 | final int? length; 5 | final T value; 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /lib/src/models/structs/image_url.dart: -------------------------------------------------------------------------------- 1 | class ImageUrl { 2 | const ImageUrl( 3 | this.url, 4 | this.styleString, 5 | ); 6 | 7 | final String url; 8 | final String styleString; 9 | } 10 | -------------------------------------------------------------------------------- /example/android/app/src/main/kotlin/com/example/example/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.app 2 | 3 | import io.flutter.embedding.android.FlutterActivity 4 | 5 | class MainActivity: FlutterActivity() { 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zyfei/flutter-quill/master/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /lib/src/models/structs/history_changed.dart: -------------------------------------------------------------------------------- 1 | class HistoryChanged { 2 | const HistoryChanged( 3 | this.changed, 4 | this.len, 5 | ); 6 | 7 | final bool changed; 8 | final int? len; 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/models/structs/vertical_spacing.dart: -------------------------------------------------------------------------------- 1 | class VerticalSpacing { 2 | const VerticalSpacing( 3 | this.top, 4 | this.bottom, 5 | ); 6 | 7 | final double top; 8 | final double bottom; 9 | } 10 | -------------------------------------------------------------------------------- /example/linux/main.cc: -------------------------------------------------------------------------------- 1 | #include "my_application.h" 2 | 3 | int main(int argc, char** argv) { 4 | g_autoptr(MyApplication) app = my_application_new(); 5 | return g_application_run(G_APPLICATION(app), argc, argv); 6 | } 7 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /lib/src/utils/experimental.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart' show immutable; 2 | 3 | @immutable 4 | class Experimental { 5 | const Experimental([this.reason = 'Experimental feature']); 6 | final String reason; 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/models/structs/link_dialog_action.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class LinkDialogAction { 4 | LinkDialogAction({required this.builder}); 5 | 6 | Widget Function(bool canPress, void Function() applyLink) builder; 7 | } 8 | -------------------------------------------------------------------------------- /lib/extensions.dart: -------------------------------------------------------------------------------- 1 | library flutter_quill.extensions; 2 | 3 | export 'src/models/documents/nodes/leaf.dart'; 4 | export 'src/models/rules/insert.dart'; 5 | export 'src/utils/platform.dart'; 6 | export 'src/utils/string.dart'; 7 | export 'src/utils/widgets.dart'; 8 | -------------------------------------------------------------------------------- /example/lib/universal_ui/real_ui.dart: -------------------------------------------------------------------------------- 1 | import 'dart:ui' if (dart.library.html) 'dart:ui_web' as ui; 2 | 3 | class PlatformViewRegistry { 4 | static void registerViewFactory(String viewId, dynamic cb) { 5 | ui.platformViewRegistry.registerViewFactory(viewId, cb); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /lib/src/models/structs/segment_leaf_node.dart: -------------------------------------------------------------------------------- 1 | import '../documents/nodes/leaf.dart'; 2 | import '../documents/nodes/line.dart'; 3 | 4 | class SegmentLeafNode { 5 | const SegmentLeafNode(this.line, this.leaf); 6 | 7 | final Line? line; 8 | final Leaf? leaf; 9 | } 10 | -------------------------------------------------------------------------------- /example/ios/Runner/main.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | #import "AppDelegate.h" 4 | 5 | int main(int argc, char* argv[]) { 6 | @autoreleasepool { 7 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/macos/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | @NSApplicationMain 5 | class AppDelegate: FlutterAppDelegate { 6 | override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { 7 | return true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-all.zip 7 | -------------------------------------------------------------------------------- /example/macos/Runner/Release.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreviewsEnabled 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: 84f3d28555368a70270e9ac8390a9441df95e752 8 | channel: stable 9 | 10 | project_type: app 11 | -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | gradle-wrapper.jar 2 | /.gradle 3 | /captures/ 4 | /gradlew 5 | /gradlew.bat 6 | /local.properties 7 | GeneratedPluginRegistrant.java 8 | 9 | # Remember to never publicly share your keystore. 10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app 11 | key.properties 12 | **/*.keystore 13 | **/*.jks 14 | -------------------------------------------------------------------------------- /flutter_quill_extensions/.metadata: -------------------------------------------------------------------------------- 1 | # This file tracks properties of this Flutter project. 2 | # Used by Flutter tool to assess capabilities and perform upgrades etc. 3 | # 4 | # This file should be version controlled and should not be manually edited. 5 | 6 | version: 7 | revision: f1875d570e39de09040c8f79aa13cc56baab8db1 8 | channel: stable 9 | 10 | project_type: package 11 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md: -------------------------------------------------------------------------------- 1 | # Launch Screen Assets 2 | 3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory. 4 | 5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. -------------------------------------------------------------------------------- /example/windows/.gitignore: -------------------------------------------------------------------------------- 1 | flutter/ephemeral/ 2 | 3 | # Visual Studio user-specific files. 4 | *.suo 5 | *.user 6 | *.userosscache 7 | *.sln.docstates 8 | 9 | # Visual Studio build-related files. 10 | x64/ 11 | x86/ 12 | 13 | # Visual Studio cache files 14 | # files ending in .cache can be ignored 15 | *.[Cc]ache 16 | # but keep track of directories ending in .cache 17 | !*.[Cc]ache/ 18 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void fl_register_plugins(FlPluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.h: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #ifndef GENERATED_PLUGIN_REGISTRANT_ 8 | #define GENERATED_PLUGIN_REGISTRANT_ 9 | 10 | #include 11 | 12 | // Registers Flutter plugins. 13 | void RegisterPlugins(flutter::PluginRegistry* registry); 14 | 15 | #endif // GENERATED_PLUGIN_REGISTRANT_ 16 | -------------------------------------------------------------------------------- /lib/src/models/structs/doc_change.dart: -------------------------------------------------------------------------------- 1 | import '../documents/document.dart'; 2 | import '../quill_delta.dart'; 3 | 4 | class DocChange { 5 | DocChange( 6 | this.before, 7 | this.change, 8 | this.source, 9 | ); 10 | 11 | /// Document state before [change]. 12 | final Delta before; 13 | 14 | /// Change delta applied to the document. 15 | final Delta change; 16 | 17 | /// The source of this change. 18 | final ChangeSource source; 19 | } 20 | -------------------------------------------------------------------------------- /example/android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /example/android/app/src/profile/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /lib/src/models/structs/optional_size.dart: -------------------------------------------------------------------------------- 1 | class OptionalSize { 2 | OptionalSize( 3 | this.width, 4 | this.height, 5 | ); 6 | 7 | /// If non-null, requires the child to have exactly this width. 8 | /// If null, the child is free to choose its own width. 9 | final double? width; 10 | 11 | /// If non-null, requires the child to have exactly this height. 12 | /// If null, the child is free to choose its own height. 13 | final double? height; 14 | } 15 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import Flutter 3 | 4 | @UIApplicationMain 5 | @objc class AppDelegate: FlutterAppDelegate { 6 | override func application( 7 | _ application: UIApplication, 8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/ios/Runner/AppDelegate.m: -------------------------------------------------------------------------------- 1 | #import "AppDelegate.h" 2 | #import "GeneratedPluginRegistrant.h" 3 | 4 | @implementation AppDelegate 5 | 6 | - (BOOL)application:(UIApplication *)application 7 | didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { 8 | [GeneratedPluginRegistrant registerWithRegistry:self]; 9 | // Override point for customization after application launch. 10 | return [super application:application didFinishLaunchingWithOptions:launchOptions]; 11 | } 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /example/linux/my_application.h: -------------------------------------------------------------------------------- 1 | #ifndef FLUTTER_MY_APPLICATION_H_ 2 | #define FLUTTER_MY_APPLICATION_H_ 3 | 4 | #include 5 | 6 | G_DECLARE_FINAL_TYPE(MyApplication, my_application, MY, APPLICATION, 7 | GtkApplication) 8 | 9 | /** 10 | * my_application_new: 11 | * 12 | * Creates a new Flutter-based application. 13 | * 14 | * Returns: a new #MyApplication. 15 | */ 16 | MyApplication* my_application_new(); 17 | 18 | #endif // FLUTTER_MY_APPLICATION_H_ 19 | -------------------------------------------------------------------------------- /example/macos/Runner/MainFlutterWindow.swift: -------------------------------------------------------------------------------- 1 | import Cocoa 2 | import FlutterMacOS 3 | 4 | class MainFlutterWindow: NSWindow { 5 | override func awakeFromNib() { 6 | let flutterViewController = FlutterViewController.init() 7 | let windowFrame = self.frame 8 | self.contentViewController = flutterViewController 9 | self.setFrame(windowFrame, display: true) 10 | 11 | RegisterGeneratedPlugins(registry: flutterViewController) 12 | 13 | super.awakeFromNib() 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /example/macos/Runner/DebugProfile.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | com.apple.security.network.server 10 | 11 | com.apple.security.network.client 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties") 4 | def properties = new Properties() 5 | 6 | assert localPropertiesFile.exists() 7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } 8 | 9 | def flutterSdkPath = properties.getProperty("flutter.sdk") 10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties" 11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" 12 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/windows/runner/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by Runner.rc 4 | // 5 | #define IDI_APP_ICON 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 102 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable-v21/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "LaunchImage.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "LaunchImage@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "filename" : "LaunchImage@3x.png", 16 | "scale" : "3x" 17 | } 18 | ], 19 | "info" : { 20 | "version" : 1, 21 | "author" : "xcode" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/utils/font.dart: -------------------------------------------------------------------------------- 1 | dynamic getFontSize(dynamic sizeValue) { 2 | if (sizeValue is String && 3 | ['small', 'normal', 'large', 'huge'].contains(sizeValue)) { 4 | return sizeValue; 5 | } 6 | 7 | if (sizeValue is double) { 8 | return sizeValue; 9 | } 10 | 11 | if (sizeValue is int) { 12 | return sizeValue.toDouble(); 13 | } 14 | 15 | assert(sizeValue is String); 16 | final fontSize = double.tryParse(sizeValue); 17 | if (fontSize == null) { 18 | throw 'Invalid size $sizeValue'; 19 | } 20 | return fontSize; 21 | } 22 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # app 2 | 3 | demo app 4 | 5 | ## Getting Started 6 | 7 | This project is a starting point for a Flutter application. 8 | 9 | A few resources to get you started if this is your first Flutter project: 10 | 11 | - [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) 12 | - [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) 13 | 14 | For help getting started with Flutter, view our 15 | [online documentation](https://flutter.dev/docs), which offers tutorials, 16 | samples, guidance on mobile development, and a full API reference. 17 | -------------------------------------------------------------------------------- /lib/src/models/config/editor/element_options.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/foundation.dart' show immutable; 3 | 4 | import 'elements/code_block.dart'; 5 | 6 | export 'elements/code_block.dart'; 7 | 8 | @immutable 9 | class QuillEditorElementOptions extends Equatable { 10 | const QuillEditorElementOptions({ 11 | this.codeBlock = const QuillEditorCodeBlockElementOptions(), 12 | }); 13 | 14 | final QuillEditorCodeBlockElementOptions codeBlock; 15 | @override 16 | List get props => [ 17 | codeBlock, 18 | ]; 19 | } 20 | -------------------------------------------------------------------------------- /flutter_quill_extensions/README.md: -------------------------------------------------------------------------------- 1 | # Flutter Quill Extensions 2 | 3 | Helpers to support embed widgets in flutter_quill. See [Flutter Quill](https://pub.dev/packages/flutter_quill) for details of use. 4 | 5 | ## Usage 6 | 7 | Set the `embedBuilders` and `embedToolbar` params in `QuillEditor` and `QuillToolbar` with the 8 | values provided by this repository. 9 | 10 | ``` 11 | QuillEditor.basic( 12 | controller: controller, 13 | embedBuilders: FlutterQuillEmbeds.builders(), 14 | ); 15 | ``` 16 | 17 | ``` 18 | QuillToolbar.basic( 19 | controller: controller, 20 | embedButtons: FlutterQuillEmbeds.buttons(), 21 | ); 22 | ``` 23 | -------------------------------------------------------------------------------- /lib/src/models/config/editor/elements/code_block.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/foundation.dart' show immutable; 3 | 4 | @immutable 5 | class QuillEditorCodeBlockElementOptions extends Equatable { 6 | const QuillEditorCodeBlockElementOptions({ 7 | this.enableLineNumbers = false, 8 | }); 9 | 10 | /// If you want line numbers in the code block, please pass true 11 | /// by default it's false as it's not really needed in most cases 12 | final bool enableLineNumbers; 13 | 14 | @override 15 | List get props => [ 16 | enableLineNumbers, 17 | ]; 18 | } 19 | -------------------------------------------------------------------------------- /example/web/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "app", 3 | "short_name": "app", 4 | "start_url": ".", 5 | "display": "standalone", 6 | "background_color": "#0175C2", 7 | "theme_color": "#0175C2", 8 | "description": "A new Flutter project.", 9 | "orientation": "portrait-primary", 10 | "prefer_related_applications": false, 11 | "icons": [ 12 | { 13 | "src": "icons/Icon-192.png", 14 | "sizes": "192x192", 15 | "type": "image/png" 16 | }, 17 | { 18 | "src": "icons/Icon-512.png", 19 | "sizes": "512x512", 20 | "type": "image/png" 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/Warnings.xcconfig: -------------------------------------------------------------------------------- 1 | WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings 2 | GCC_WARN_UNDECLARED_SELECTOR = YES 3 | CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES 4 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE 5 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES 6 | CLANG_WARN_PRAGMA_PACK = YES 7 | CLANG_WARN_STRICT_PROTOTYPES = YES 8 | CLANG_WARN_COMMA = YES 9 | GCC_WARN_STRICT_SELECTOR_MATCH = YES 10 | CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES 11 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES 12 | GCC_WARN_SHADOW = YES 13 | CLANG_WARN_UNREACHABLE_CODE = YES 14 | -------------------------------------------------------------------------------- /lib/src/models/themes/quill_custom_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../widgets/toolbar/base_toolbar.dart'; 4 | 5 | class QuillCustomButton extends QuillToolbarBaseButtonOptions { 6 | const QuillCustomButton({ 7 | super.iconData, 8 | this.iconColor, 9 | this.onTap, 10 | super.tooltip, 11 | this.iconSize, 12 | this.child, 13 | super.iconTheme, 14 | }); 15 | 16 | ///The icon color; 17 | final Color? iconColor; 18 | 19 | ///The function when the icon is tapped 20 | final VoidCallback? onTap; 21 | 22 | ///The customButton placeholder 23 | final Widget? child; 24 | 25 | final double? iconSize; 26 | } 27 | -------------------------------------------------------------------------------- /before-push.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Run Flutter analyze 4 | echo "Running 'flutter analyze'..." 5 | flutter analyze 6 | 7 | # Run Flutter test 8 | echo "Running 'flutter test'..." 9 | flutter test 10 | 11 | # Check if package is ready for publishing 12 | echo "Running 'flutter pub publish --dry-run'..." 13 | flutter pub publish --dry-run 14 | 15 | # Apply Dart fixes 16 | echo "Running 'dart fix --apply'..." 17 | dart fix --apply 18 | 19 | # Format Dart code 20 | echo "Running 'dart format .'" 21 | dart format . 22 | 23 | # Check dart code formatting 24 | echo "Running `dart format --set-exit-if-changed .`" 25 | dart format --set-exit-if-changed . 26 | 27 | echo "Script completed." 28 | -------------------------------------------------------------------------------- /lib/src/utils/extensions/quill_controller.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show BuildContext; 2 | 3 | import '../../../flutter_quill.dart' show QuillController, QuillProvider; 4 | import 'build_context.dart'; 5 | 6 | extension QuillControllerNullableExt on QuillController? { 7 | /// Simple logic to use the current passed controller if not null 8 | /// if null then we will have to use the default one from [QuillProvider] 9 | /// using the [context] 10 | QuillController notNull(BuildContext context) { 11 | final controller = this; 12 | if (controller != null) { 13 | return controller; 14 | } 15 | return context.requireQuillController; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/src/widgets/style_widgets/bullet_point.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class QuillBulletPoint extends StatelessWidget { 4 | const QuillBulletPoint({ 5 | required this.style, 6 | required this.width, 7 | this.padding = 0, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final TextStyle style; 12 | final double width; 13 | final double padding; 14 | 15 | @override 16 | Widget build(BuildContext context) { 17 | return Container( 18 | alignment: AlignmentDirectional.topEnd, 19 | width: width, 20 | padding: EdgeInsetsDirectional.only(end: padding), 21 | child: Text('•', style: style), 22 | ); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /example/macos/Runner/Configs/AppInfo.xcconfig: -------------------------------------------------------------------------------- 1 | // Application-level settings for the Runner target. 2 | // 3 | // This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the 4 | // future. If not, the values below would default to using the project name when this becomes a 5 | // 'flutter create' template. 6 | 7 | // The application's name. By default this is also the title of the Flutter window. 8 | PRODUCT_NAME = app 9 | 10 | // The application's bundle identifier 11 | PRODUCT_BUNDLE_IDENTIFIER = com.example.app 12 | 13 | // The copyright displayed in application information 14 | PRODUCT_COPYRIGHT = Copyright © 2021 com.example. All rights reserved. 15 | -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | *.mode1v3 2 | *.mode2v3 3 | *.moved-aside 4 | *.pbxuser 5 | *.perspectivev3 6 | **/*sync/ 7 | .sconsign.dblite 8 | .tags* 9 | **/.vagrant/ 10 | **/DerivedData/ 11 | Icon? 12 | **/Pods/ 13 | **/.symlinks/ 14 | profile 15 | xcuserdata 16 | **/.generated/ 17 | Flutter/App.framework 18 | Flutter/Flutter.framework 19 | Flutter/Flutter.podspec 20 | Flutter/Generated.xcconfig 21 | Flutter/app.flx 22 | Flutter/app.zip 23 | Flutter/flutter_assets/ 24 | Flutter/flutter_export_environment.sh 25 | ServiceDefinitions.json 26 | Runner/GeneratedPluginRegistrant.* 27 | 28 | # Exceptions to above rules. 29 | !default.mode1v3 30 | !default.mode2v3 31 | !default.pbxuser 32 | !default.perspectivev3 33 | -------------------------------------------------------------------------------- /lib/src/utils/embeds.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math'; 2 | 3 | import '../models/documents/nodes/leaf.dart'; 4 | import '../models/structs/offset_value.dart'; 5 | import '../widgets/controller.dart'; 6 | 7 | OffsetValue getEmbedNode(QuillController controller, int offset) { 8 | var offset = controller.selection.start; 9 | var embedNode = controller.queryNode(offset); 10 | if (embedNode == null || !(embedNode is Embed)) { 11 | offset = max(0, offset - 1); 12 | embedNode = controller.queryNode(offset); 13 | } 14 | if (embedNode != null && embedNode is Embed) { 15 | return OffsetValue(offset, embedNode); 16 | } 17 | 18 | return throw 'Embed node not found by offset $offset'; 19 | } 20 | -------------------------------------------------------------------------------- /example/windows/runner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(runner LANGUAGES CXX) 3 | 4 | add_executable(${BINARY_NAME} WIN32 5 | "flutter_window.cpp" 6 | "main.cpp" 7 | "run_loop.cpp" 8 | "utils.cpp" 9 | "win32_window.cpp" 10 | "${FLUTTER_MANAGED_DIR}/generated_plugin_registrant.cc" 11 | "Runner.rc" 12 | "runner.exe.manifest" 13 | ) 14 | apply_standard_settings(${BINARY_NAME}) 15 | target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX") 16 | target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app) 17 | target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}") 18 | add_dependencies(${BINARY_NAME} flutter_assemble) 19 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.9.0' 3 | repositories { 4 | google() 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:7.4.0' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | mavenCentral() 18 | } 19 | } 20 | 21 | rootProject.buildDir = '../build' 22 | subprojects { 23 | project.buildDir = "${rootProject.buildDir}/${project.name}" 24 | } 25 | subprojects { 26 | project.evaluationDependsOn(':app') 27 | } 28 | 29 | tasks.register("clean", Delete) { 30 | delete rootProject.buildDir 31 | } 32 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/indent.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart'; 2 | 3 | import 'base.dart'; 4 | 5 | class QuillToolbarIndentButtonExtraOptions 6 | extends QuillToolbarBaseButtonExtraOptions { 7 | const QuillToolbarIndentButtonExtraOptions({ 8 | required super.controller, 9 | required super.context, 10 | required super.onPressed, 11 | }); 12 | } 13 | 14 | @immutable 15 | class QuillToolbarIndentButtonOptions extends QuillToolbarBaseButtonOptions { 16 | const QuillToolbarIndentButtonOptions({ 17 | super.iconData, 18 | super.afterButtonPressed, 19 | super.childBuilder, 20 | super.controller, 21 | super.iconTheme, 22 | super.tooltip, 23 | this.iconSize, 24 | }); 25 | 26 | final double? iconSize; 27 | } 28 | -------------------------------------------------------------------------------- /example/windows/runner/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_UTILS_H_ 2 | #define RUNNER_UTILS_H_ 3 | 4 | #include 5 | #include 6 | 7 | // Creates a console for the process, and redirects stdout and stderr to 8 | // it for both the runner and the Flutter library. 9 | void CreateAndAttachConsole(); 10 | 11 | // Takes a null-terminated wchar_t* encoded in UTF-16 and returns a std::string 12 | // encoded in UTF-8. Returns an empty std::string on failure. 13 | std::string Utf8FromUtf16(const wchar_t* utf16_string); 14 | 15 | // Gets the command line arguments passed in as a std::vector, 16 | // encoded in UTF-8. Returns an empty std::vector on failure. 17 | std::vector GetCommandLineArguments(); 18 | 19 | #endif // RUNNER_UTILS_H_ 20 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | #.vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | **/ios/Flutter/.last_build_id 26 | .dart_tool/ 27 | .flutter-plugins 28 | .flutter-plugins-dependencies 29 | .packages 30 | .pub-cache/ 31 | .pub/ 32 | /build/ 33 | 34 | # Web related 35 | lib/generated_plugin_registrant.dart 36 | 37 | # Symbolication related 38 | app.*.symbols 39 | 40 | # Obfuscation related 41 | app.*.map.json 42 | pubspec.lock -------------------------------------------------------------------------------- /lib/src/utils/widgets.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | typedef WidgetWrapper = Widget Function(Widget child); 4 | 5 | /// Provides utiulity widgets. 6 | abstract class UtilityWidgets { 7 | /// Conditionally wraps the [child] with [Tooltip] widget if [message] 8 | /// is not null and not empty. 9 | static Widget maybeTooltip({ 10 | required Widget child, 11 | String? message, 12 | }) => 13 | (message?.isNotEmpty ?? false) 14 | ? Tooltip(message: message, child: child) 15 | : child; 16 | 17 | /// Conditionally wraps the [child] with [wrapper] widget if [enabled] 18 | /// is true. 19 | static Widget maybeWidget({ 20 | required WidgetWrapper wrapper, 21 | required Widget child, 22 | bool enabled = false, 23 | }) => 24 | enabled ? wrapper(child) : child; 25 | } 26 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/clear_format.dart: -------------------------------------------------------------------------------- 1 | import '../../quill_configurations.dart'; 2 | 3 | class QuillToolbarClearFormatButtonExtraOptions 4 | extends QuillToolbarBaseButtonExtraOptions { 5 | const QuillToolbarClearFormatButtonExtraOptions({ 6 | required super.controller, 7 | required super.context, 8 | required super.onPressed, 9 | }); 10 | } 11 | 12 | class QuillToolbarClearFormatButtonOptions 13 | extends QuillToolbarBaseButtonOptions { 15 | const QuillToolbarClearFormatButtonOptions({ 16 | super.iconData, 17 | super.afterButtonPressed, 18 | super.childBuilder, 19 | super.controller, 20 | super.iconTheme, 21 | super.tooltip, 22 | this.iconSize, 23 | }); 24 | 25 | final double? iconSize; 26 | } 27 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | void RegisterPlugins(flutter::PluginRegistry* registry) { 15 | FileSelectorWindowsRegisterWithRegistrar( 16 | registry->GetRegistrarForPlugin("FileSelectorWindows")); 17 | GalPluginCApiRegisterWithRegistrar( 18 | registry->GetRegistrarForPlugin("GalPluginCApi")); 19 | PasteboardPluginRegisterWithRegistrar( 20 | registry->GetRegistrarForPlugin("PasteboardPlugin")); 21 | UrlLauncherWindowsRegisterWithRegistrar( 22 | registry->GetRegistrarForPlugin("UrlLauncherWindows")); 23 | } 24 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/issue-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Issue template 3 | about: Common things to fill 4 | title: "[Web] or [Mobile] or [Desktop]" 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | My issue is about [Web] 11 | My issue is about [Mobile] 12 | My issue is about [Desktop] 13 | 14 | I have tried running `example` directory successfully before creating an issue here. 15 | 16 | Please note that we are using latest flutter version in stable channel on branch master. If you are using beta or master channel, or you are not using latest flutter version in stable channel, you may experience error. 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /example/ios/Flutter/AppFrameworkInfo.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | App 9 | CFBundleIdentifier 10 | io.flutter.flutter.app 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | App 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1.0 23 | MinimumOSVersion 24 | 11.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_linux 7 | pasteboard 8 | url_launcher_linux 9 | ) 10 | 11 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 12 | ) 13 | 14 | set(PLUGIN_BUNDLED_LIBRARIES) 15 | 16 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 17 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/linux plugins/${plugin}) 18 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 19 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 21 | endforeach(plugin) 22 | 23 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 24 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/linux plugins/${ffi_plugin}) 25 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 26 | endforeach(ffi_plugin) 27 | -------------------------------------------------------------------------------- /example/windows/flutter/generated_plugins.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Generated file, do not edit. 3 | # 4 | 5 | list(APPEND FLUTTER_PLUGIN_LIST 6 | file_selector_windows 7 | gal 8 | pasteboard 9 | url_launcher_windows 10 | ) 11 | 12 | list(APPEND FLUTTER_FFI_PLUGIN_LIST 13 | ) 14 | 15 | set(PLUGIN_BUNDLED_LIBRARIES) 16 | 17 | foreach(plugin ${FLUTTER_PLUGIN_LIST}) 18 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${plugin}/windows plugins/${plugin}) 19 | target_link_libraries(${BINARY_NAME} PRIVATE ${plugin}_plugin) 20 | list(APPEND PLUGIN_BUNDLED_LIBRARIES $) 21 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${plugin}_bundled_libraries}) 22 | endforeach(plugin) 23 | 24 | foreach(ffi_plugin ${FLUTTER_FFI_PLUGIN_LIST}) 25 | add_subdirectory(flutter/ephemeral/.plugin_symlinks/${ffi_plugin}/windows plugins/${ffi_plugin}) 26 | list(APPEND PLUGIN_BUNDLED_LIBRARIES ${${ffi_plugin}_bundled_libraries}) 27 | endforeach(ffi_plugin) 28 | -------------------------------------------------------------------------------- /example/windows/runner/runner.exe.manifest: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PerMonitorV2 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /lib/src/widgets/float_cursor.dart: -------------------------------------------------------------------------------- 1 | // The corner radius of the floating cursor in pixels. 2 | import 'dart:ui'; 3 | 4 | import 'cursor.dart'; 5 | 6 | const Radius _kFloatingCaretRadius = Radius.circular(1); 7 | 8 | /// Floating painter responsible for painting the floating cursor when 9 | /// floating mode is activated 10 | class FloatingCursorPainter { 11 | FloatingCursorPainter({ 12 | required this.floatingCursorRect, 13 | required this.style, 14 | }); 15 | 16 | CursorStyle style; 17 | 18 | Rect? floatingCursorRect; 19 | 20 | final Paint floatingCursorPaint = Paint(); 21 | 22 | void paint(Canvas canvas) { 23 | final floatingCursorRect = this.floatingCursorRect; 24 | final floatingCursorColor = style.color.withOpacity(0.75); 25 | if (floatingCursorRect == null) return; 26 | canvas.drawRRect( 27 | RRect.fromRectAndRadius(floatingCursorRect, _kFloatingCaretRadius), 28 | floatingCursorPaint..color = floatingCursorColor, 29 | ); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_localizations/flutter_localizations.dart'; 3 | 4 | import 'pages/home_page.dart'; 5 | 6 | void main() { 7 | WidgetsFlutterBinding.ensureInitialized(); 8 | runApp(MyApp()); 9 | } 10 | 11 | class MyApp extends StatelessWidget { 12 | @override 13 | Widget build(BuildContext context) { 14 | return MaterialApp( 15 | debugShowCheckedModeBanner: false, 16 | title: 'Quill Demo', 17 | theme: ThemeData( 18 | primarySwatch: Colors.blue, 19 | visualDensity: VisualDensity.adaptivePlatformDensity, 20 | ), 21 | localizationsDelegates: [ 22 | GlobalMaterialLocalizations.delegate, 23 | GlobalWidgetsLocalizations.delegate, 24 | GlobalCupertinoLocalizations.delegate, 25 | ], 26 | supportedLocales: [ 27 | const Locale('en', 'US'), 28 | const Locale('zh', 'HK'), 29 | ], 30 | home: HomePage(), 31 | ); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: flutter-quill CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: subosito/flutter-action@v2 16 | with: 17 | channel: 'stable' 18 | 19 | - name: Check flutter version 20 | run: flutter --version 21 | 22 | - name: Install dependencies 23 | run: flutter pub get 24 | 25 | - name: Install flutter_quill_extensions dependencies 26 | run: flutter pub get -C flutter_quill_extensions 27 | 28 | - name: Run flutter analysis 29 | run: flutter analyze 30 | 31 | - name: Check dart code formatting 32 | run: dart format --set-exit-if-changed . 33 | 34 | - name: Check if package is ready for publishing 35 | run: flutter pub publish --dry-run 36 | 37 | - name: Run flutter tests 38 | run: flutter test 39 | -------------------------------------------------------------------------------- /lib/src/widgets/embeds.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../extensions.dart'; 4 | import '../models/documents/nodes/leaf.dart' as leaf; 5 | import '../models/themes/quill_dialog_theme.dart'; 6 | import '../models/themes/quill_icon_theme.dart'; 7 | import 'controller.dart'; 8 | 9 | abstract class EmbedBuilder { 10 | const EmbedBuilder(); 11 | 12 | String get key; 13 | bool get expanded => true; 14 | 15 | WidgetSpan buildWidgetSpan(Widget widget) { 16 | return WidgetSpan(child: widget); 17 | } 18 | 19 | String toPlainText(Embed node) => Embed.kObjectReplacementCharacter; 20 | 21 | Widget build( 22 | BuildContext context, 23 | QuillController controller, 24 | leaf.Embed node, 25 | bool readOnly, 26 | bool inline, 27 | TextStyle textStyle, 28 | ); 29 | } 30 | 31 | typedef EmbedButtonBuilder = Widget Function( 32 | QuillController controller, 33 | double toolbarIconSize, 34 | QuillIconTheme? iconTheme, 35 | QuillDialogTheme? dialogTheme); 36 | -------------------------------------------------------------------------------- /lib/src/models/config/others/animations.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/foundation.dart' show immutable; 3 | 4 | import '../../../utils/experimental.dart'; 5 | 6 | @immutable 7 | @Experimental('This class might removed') 8 | class QuillAnimationConfigurations extends Equatable { 9 | const QuillAnimationConfigurations({ 10 | required this.checkBoxPointItem, 11 | }); 12 | 13 | factory QuillAnimationConfigurations.disableAll() => 14 | const QuillAnimationConfigurations( 15 | checkBoxPointItem: false, 16 | ); 17 | 18 | factory QuillAnimationConfigurations.enableAll() => 19 | const QuillAnimationConfigurations( 20 | checkBoxPointItem: true, 21 | ); 22 | 23 | /// This currently has issue which the whole checkbox list will rebuilt 24 | /// and the animation will replay when some value changes 25 | /// which is why disabled by default 26 | final bool checkBoxPointItem; 27 | 28 | @override 29 | List get props => []; 30 | } 31 | -------------------------------------------------------------------------------- /flutter_quill_extensions/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_quill_extensions 2 | description: Embed extensions for flutter_quill including image, video, formula and etc. 3 | version: 0.5.1 4 | homepage: https://bulletjournal.us/home/index.html 5 | repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions 6 | platforms: 7 | android: 8 | ios: 9 | # linux: 10 | macos: 11 | web: 12 | windows: 13 | 14 | environment: 15 | sdk: ">=2.17.0 <4.0.0" 16 | flutter: ">=3.0.0" 17 | 18 | dependencies: 19 | flutter: 20 | sdk: flutter 21 | 22 | flutter_quill: ^7.8.0 23 | # In case you are working on changes for both libraries, 24 | # flutter_quill: 25 | # path: ../ 26 | 27 | http: ^1.1.0 28 | image_picker: ">=1.0.4" 29 | photo_view: ^0.14.0 30 | video_player: ^2.7.2 31 | youtube_player_flutter: ^8.1.2 32 | math_keyboard: ">=0.2.1" 33 | universal_html: ^2.2.4 34 | 35 | gal: ^2.1.2 36 | 37 | dev_dependencies: 38 | flutter_test: 39 | sdk: flutter 40 | pedantic: ^1.11.1 41 | 42 | flutter: 43 | -------------------------------------------------------------------------------- /example/macos/Flutter/GeneratedPluginRegistrant.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | import FlutterMacOS 6 | import Foundation 7 | 8 | import device_info_plus 9 | import file_selector_macos 10 | import gal 11 | import pasteboard 12 | import path_provider_foundation 13 | import url_launcher_macos 14 | import video_player_avfoundation 15 | 16 | func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { 17 | DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) 18 | FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) 19 | GalPlugin.register(with: registry.registrar(forPlugin: "GalPlugin")) 20 | PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) 21 | PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) 22 | UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) 23 | FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) 24 | } 25 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /example/linux/flutter/generated_plugin_registrant.cc: -------------------------------------------------------------------------------- 1 | // 2 | // Generated file. Do not edit. 3 | // 4 | 5 | // clang-format off 6 | 7 | #include "generated_plugin_registrant.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | void fl_register_plugins(FlPluginRegistry* registry) { 14 | g_autoptr(FlPluginRegistrar) file_selector_linux_registrar = 15 | fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin"); 16 | file_selector_plugin_register_with_registrar(file_selector_linux_registrar); 17 | g_autoptr(FlPluginRegistrar) pasteboard_registrar = 18 | fl_plugin_registry_get_registrar_for_plugin(registry, "PasteboardPlugin"); 19 | pasteboard_plugin_register_with_registrar(pasteboard_registrar); 20 | g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = 21 | fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); 22 | url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); 23 | } 24 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/toggle_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart' show immutable; 2 | import 'package:flutter/widgets.dart' show Color; 3 | 4 | import 'base.dart'; 5 | 6 | class QuillToolbarToggleStyleButtonExtraOptions 7 | extends QuillToolbarBaseButtonExtraOptions { 8 | const QuillToolbarToggleStyleButtonExtraOptions({ 9 | required super.controller, 10 | required super.context, 11 | required super.onPressed, 12 | required this.isToggled, 13 | }); 14 | 15 | final bool isToggled; 16 | } 17 | 18 | @immutable 19 | class QuillToolbarToggleStyleButtonOptions 20 | extends QuillToolbarBaseButtonOptions { 22 | const QuillToolbarToggleStyleButtonOptions({ 23 | super.iconData, 24 | this.iconSize, 25 | this.fillColor, 26 | super.tooltip, 27 | super.afterButtonPressed, 28 | super.iconTheme, 29 | super.childBuilder, 30 | super.controller, 31 | }); 32 | 33 | final double? iconSize; 34 | final Color? fillColor; 35 | } 36 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: flutter_quill 2 | 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. 3 | version: 8.1.0 4 | homepage: https://1o24bbs.com/c/bulletjournal/108 5 | repository: https://github.com/singerdmx/flutter-quill 6 | topics: 7 | - ui 8 | - widgets 9 | - widget 10 | - rich-text-editor 11 | platforms: 12 | android: 13 | ios: 14 | linux: 15 | macos: 16 | web: 17 | windows: 18 | 19 | environment: 20 | sdk: ">=2.17.0 <4.0.0" 21 | flutter: ">=3.10.0" 22 | 23 | dependencies: 24 | flutter: 25 | sdk: flutter 26 | collection: ^1.17.0 27 | flutter_colorpicker: ^1.0.3 28 | flutter_keyboard_visibility: ^5.4.1 29 | quiver: ^3.2.1 30 | url_launcher: ^6.1.14 31 | pedantic: ^1.11.1 32 | characters: ^1.3.0 33 | diff_match_patch: ^0.4.1 34 | i18n_extension: ^9.0.2 35 | device_info_plus: ^9.1.0 36 | platform: ^3.1.3 37 | pasteboard: ^0.2.0 38 | equatable: ^2.0.5 39 | flutter_animate: ^4.2.0+1 40 | 41 | flutter_test: 42 | sdk: flutter 43 | 44 | flutter: null -------------------------------------------------------------------------------- /example/android/app/src/main/res/values-night/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 15 | 18 | 19 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/shims/dart_ui_fake.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_classes_with_only_static_members, camel_case_types, lines_longer_than_80_chars 2 | 3 | import 'package:universal_html/html.dart' as html; 4 | 5 | // Fake interface for the logic that this package needs from (web-only) dart:ui. 6 | // This is conditionally exported so the analyzer sees these methods as available. 7 | 8 | typedef PlatroformViewFactory = html.Element Function(int viewId); 9 | 10 | /// Shim for web_ui engine.PlatformViewRegistry 11 | /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L62 12 | class platformViewRegistry { 13 | /// Shim for registerViewFactory 14 | /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/ui.dart#L72 15 | static dynamic registerViewFactory( 16 | String viewTypeId, PlatroformViewFactory viewFactory) {} 17 | } 18 | 19 | /// Shim for web_ui engine.AssetManager 20 | /// https://github.com/flutter/engine/blob/master/lib/web_ui/lib/src/engine/assets.dart#L12 21 | class webOnlyAssetManager { 22 | static dynamic getAssetUrl(String asset) {} 23 | } 24 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Xin Yao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /lib/src/models/themes/quill_icon_theme.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class QuillIconTheme { 4 | const QuillIconTheme( 5 | {this.iconSelectedColor, 6 | this.iconUnselectedColor, 7 | this.iconSelectedFillColor, 8 | this.iconUnselectedFillColor, 9 | this.disabledIconColor, 10 | this.disabledIconFillColor, 11 | this.borderRadius}); 12 | 13 | ///The color to use for selected icons in the toolbar 14 | final Color? iconSelectedColor; 15 | 16 | ///The color to use for unselected icons in the toolbar 17 | final Color? iconUnselectedColor; 18 | 19 | ///The fill color to use for the selected icons in the toolbar 20 | final Color? iconSelectedFillColor; 21 | 22 | ///The fill color to use for the unselected icons in the toolbar 23 | final Color? iconUnselectedFillColor; 24 | 25 | ///The color to use for disabled icons in the toolbar 26 | final Color? disabledIconColor; 27 | 28 | ///The fill color to use for disabled icons in the toolbar 29 | final Color? disabledIconFillColor; 30 | 31 | ///The borderRadius for icons 32 | final double? borderRadius; 33 | } 34 | -------------------------------------------------------------------------------- /example/test/widget_test.dart: -------------------------------------------------------------------------------- 1 | // This is a basic Flutter widget test. 2 | // 3 | // To perform an interaction with a widget in your test, use the WidgetTester 4 | // utility that Flutter provides. For example, you can send tap and scroll 5 | // gestures. You can also use WidgetTester to find child widgets in the widget 6 | // tree, read text, and verify that the values of widget properties are correct. 7 | 8 | import 'package:app/main.dart'; 9 | import 'package:flutter/material.dart'; 10 | import 'package:flutter_test/flutter_test.dart'; 11 | 12 | void main() { 13 | testWidgets('Counter increments smoke test', (tester) async { 14 | // Build our app and trigger a frame. 15 | await tester.pumpWidget(MyApp()); 16 | 17 | // Verify that our counter starts at 0. 18 | expect(find.text('0'), findsOneWidget); 19 | expect(find.text('1'), findsNothing); 20 | 21 | // Tap the '+' icon and trigger a frame. 22 | await tester.tap(find.byIcon(Icons.add)); 23 | await tester.pump(); 24 | 25 | // Verify that our counter has incremented. 26 | expect(find.text('0'), findsNothing); 27 | expect(find.text('1'), findsOneWidget); 28 | }); 29 | } 30 | -------------------------------------------------------------------------------- /flutter_quill_extensions/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Xin Yao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /example/lib/widgets/time_stamp_embed_widget.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter_quill/flutter_quill.dart'; 5 | 6 | class TimeStampEmbed extends Embeddable { 7 | const TimeStampEmbed( 8 | String value, 9 | ) : super(timeStampType, value); 10 | 11 | static const String timeStampType = 'timeStamp'; 12 | 13 | static TimeStampEmbed fromDocument(Document document) => 14 | TimeStampEmbed(jsonEncode(document.toDelta().toJson())); 15 | 16 | Document get document => Document.fromJson(jsonDecode(data)); 17 | } 18 | 19 | class TimeStampEmbedBuilderWidget extends EmbedBuilder { 20 | @override 21 | String get key => 'timeStamp'; 22 | 23 | @override 24 | String toPlainText(Embed embed) { 25 | return embed.value.data; 26 | } 27 | 28 | @override 29 | Widget build( 30 | BuildContext context, 31 | QuillController controller, 32 | Embed node, 33 | bool readOnly, 34 | bool inline, 35 | TextStyle textStyle, 36 | ) { 37 | return Row( 38 | children: [ 39 | const Icon(Icons.access_time_rounded), 40 | Text(node.value.data as String), 41 | ], 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/history.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart' show immutable; 2 | 3 | import '../../../../../flutter_quill.dart'; 4 | 5 | @immutable 6 | class QuillToolbarHistoryButtonExtraOptions 7 | extends QuillToolbarBaseButtonExtraOptions { 8 | const QuillToolbarHistoryButtonExtraOptions({ 9 | required this.canPressed, 10 | required super.controller, 11 | required super.context, 12 | required super.onPressed, 13 | }); 14 | 15 | /// If it can redo or undo 16 | final bool canPressed; 17 | } 18 | 19 | @immutable 20 | class QuillToolbarHistoryButtonOptions extends QuillToolbarBaseButtonOptions< 21 | QuillToolbarHistoryButtonOptions, QuillToolbarHistoryButtonExtraOptions> { 22 | const QuillToolbarHistoryButtonOptions({ 23 | required this.isUndo, 24 | super.iconData, 25 | super.controller, 26 | super.iconTheme, 27 | super.afterButtonPressed, 28 | super.tooltip, 29 | super.childBuilder, 30 | this.iconSize, 31 | }); 32 | 33 | /// If this true then it will be the undo button 34 | /// otherwise it will be redo 35 | final bool isUndo; 36 | 37 | /// By default will use [globalIconSize] 38 | final double? iconSize; 39 | } 40 | -------------------------------------------------------------------------------- /lib/src/models/config/quill_configurations.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/foundation.dart' show immutable; 3 | 4 | import '../../../flutter_quill.dart'; 5 | 6 | export './editor/configurations.dart'; 7 | export './shared_configurations.dart'; 8 | export './toolbar/configurations.dart'; 9 | 10 | @immutable 11 | class QuillConfigurations extends Equatable { 12 | const QuillConfigurations({ 13 | required this.controller, 14 | this.sharedConfigurations = const QuillSharedConfigurations(), 15 | }); 16 | 17 | /// Controller object which establishes a link between a rich text document 18 | /// and this editor. 19 | /// 20 | /// The controller is shared between [QuillEditorConfigurations] and 21 | /// [QuillToolbarConfigurations] but to simplify things we will defined it 22 | /// here, it should not be null 23 | final QuillController controller; 24 | 25 | /// The shared configurations between [QuillEditorConfigurations] and 26 | /// [QuillToolbarConfigurations] so we don't duplicate things 27 | final QuillSharedConfigurations sharedConfigurations; 28 | 29 | @override 30 | List get props => [ 31 | sharedConfigurations, 32 | ]; 33 | } 34 | -------------------------------------------------------------------------------- /analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | undefined_prefixed_name: ignore 6 | unsafe_html: ignore 7 | linter: 8 | rules: 9 | - always_declare_return_types 10 | - always_put_required_named_parameters_first 11 | - annotate_overrides 12 | - avoid_empty_else 13 | - avoid_escaping_inner_quotes 14 | - avoid_print 15 | - avoid_redundant_argument_values 16 | - avoid_types_on_closure_parameters 17 | - avoid_void_async 18 | - cascade_invocations 19 | - directives_ordering 20 | - lines_longer_than_80_chars 21 | - omit_local_variable_types 22 | - prefer_const_constructors 23 | - prefer_const_constructors_in_immutables 24 | - prefer_const_declarations 25 | - prefer_final_fields 26 | - prefer_final_in_for_each 27 | - prefer_final_locals 28 | - prefer_initializing_formals 29 | - prefer_int_literals 30 | - prefer_interpolation_to_compose_strings 31 | - prefer_relative_imports 32 | - prefer_single_quotes 33 | - sort_constructors_first 34 | - sort_unnamed_constructors_first 35 | - unnecessary_lambdas 36 | - unnecessary_parenthesis 37 | - unnecessary_string_interpolations 38 | -------------------------------------------------------------------------------- /example/macos/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIconFile 10 | 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | $(FLUTTER_BUILD_NAME) 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | LSMinimumSystemVersion 24 | $(MACOSX_DEPLOYMENT_TARGET) 25 | NSHumanReadableCopyright 26 | $(PRODUCT_COPYRIGHT) 27 | NSMainNibFile 28 | MainMenu 29 | NSPrincipalClass 30 | NSApplication 31 | 32 | 33 | -------------------------------------------------------------------------------- /example/windows/runner/run_loop.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_RUN_LOOP_H_ 2 | #define RUNNER_RUN_LOOP_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | // A runloop that will service events for Flutter instances as well 10 | // as native messages. 11 | class RunLoop { 12 | public: 13 | RunLoop(); 14 | ~RunLoop(); 15 | 16 | // Prevent copying 17 | RunLoop(RunLoop const&) = delete; 18 | RunLoop& operator=(RunLoop const&) = delete; 19 | 20 | // Runs the run loop until the application quits. 21 | void Run(); 22 | 23 | // Registers the given Flutter instance for event servicing. 24 | void RegisterFlutterInstance( 25 | flutter::FlutterEngine* flutter_instance); 26 | 27 | // Unregisters the given Flutter instance from event servicing. 28 | void UnregisterFlutterInstance( 29 | flutter::FlutterEngine* flutter_instance); 30 | 31 | private: 32 | using TimePoint = std::chrono::steady_clock::time_point; 33 | 34 | // Processes all currently pending messages for registered Flutter instances. 35 | TimePoint ProcessFlutterMessages(); 36 | 37 | std::set flutter_instances_; 38 | }; 39 | 40 | #endif // RUNNER_RUN_LOOP_H_ 41 | -------------------------------------------------------------------------------- /flutter_quill_extensions/analysis_options.yaml: -------------------------------------------------------------------------------- 1 | include: package:pedantic/analysis_options.yaml 2 | 3 | analyzer: 4 | errors: 5 | undefined_prefixed_name: ignore 6 | unsafe_html: ignore 7 | linter: 8 | rules: 9 | - always_declare_return_types 10 | - always_put_required_named_parameters_first 11 | - annotate_overrides 12 | - avoid_empty_else 13 | - avoid_escaping_inner_quotes 14 | - avoid_print 15 | - avoid_redundant_argument_values 16 | - avoid_types_on_closure_parameters 17 | - avoid_void_async 18 | - cascade_invocations 19 | - directives_ordering 20 | - lines_longer_than_80_chars 21 | - omit_local_variable_types 22 | - prefer_const_constructors 23 | - prefer_const_constructors_in_immutables 24 | - prefer_const_declarations 25 | - prefer_final_fields 26 | - prefer_final_in_for_each 27 | - prefer_final_locals 28 | - prefer_initializing_formals 29 | - prefer_int_literals 30 | - prefer_interpolation_to_compose_strings 31 | - prefer_relative_imports 32 | - prefer_single_quotes 33 | - sort_constructors_first 34 | - sort_unnamed_constructors_first 35 | - unnecessary_lambdas 36 | - unnecessary_parenthesis 37 | - unnecessary_string_interpolations 38 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_FLUTTER_WINDOW_H_ 2 | #define RUNNER_FLUTTER_WINDOW_H_ 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include "run_loop.h" 10 | #include "win32_window.h" 11 | 12 | // A window that does nothing but host a Flutter view. 13 | class FlutterWindow : public Win32Window { 14 | public: 15 | // Creates a new FlutterWindow driven by the |run_loop|, hosting a 16 | // Flutter view running |project|. 17 | explicit FlutterWindow(RunLoop* run_loop, 18 | const flutter::DartProject& project); 19 | virtual ~FlutterWindow(); 20 | 21 | protected: 22 | // Win32Window: 23 | bool OnCreate() override; 24 | void OnDestroy() override; 25 | LRESULT MessageHandler(HWND window, UINT const message, WPARAM const wparam, 26 | LPARAM const lparam) noexcept override; 27 | 28 | private: 29 | // The run loop driving events for this window. 30 | RunLoop* run_loop_; 31 | 32 | // The project to run. 33 | flutter::DartProject project_; 34 | 35 | // The Flutter instance hosted by this window. 36 | std::unique_ptr flutter_controller_; 37 | }; 38 | 39 | #endif // RUNNER_FLUTTER_WINDOW_H_ 40 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/link_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show Color; 2 | 3 | import '../../../../widgets/toolbar/base_toolbar.dart'; 4 | import '../../../structs/link_dialog_action.dart'; 5 | import '../../../themes/quill_dialog_theme.dart'; 6 | 7 | class QuillToolbarLinkStyleButtonExtraOptions 8 | extends QuillToolbarBaseButtonExtraOptions { 9 | const QuillToolbarLinkStyleButtonExtraOptions({ 10 | required super.controller, 11 | required super.context, 12 | required super.onPressed, 13 | }); 14 | } 15 | 16 | class QuillToolbarLinkStyleButtonOptions extends QuillToolbarBaseButtonOptions< 17 | QuillToolbarLinkStyleButtonOptions, 18 | QuillToolbarLinkStyleButtonExtraOptions> { 19 | const QuillToolbarLinkStyleButtonOptions({ 20 | this.dialogTheme, 21 | this.linkRegExp, 22 | this.linkDialogAction, 23 | this.dialogBarrierColor, 24 | this.iconSize, 25 | super.iconData, 26 | super.globalIconSize, 27 | super.afterButtonPressed, 28 | super.tooltip, 29 | super.iconTheme, 30 | super.childBuilder, 31 | super.controller, 32 | }); 33 | 34 | final double? iconSize; 35 | final QuillDialogTheme? dialogTheme; 36 | final RegExp? linkRegExp; 37 | final LinkDialogAction? linkDialogAction; 38 | final Color? dialogBarrierColor; 39 | } 40 | -------------------------------------------------------------------------------- /example/lib/widgets/responsive_widget.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | class ResponsiveWidget extends StatelessWidget { 4 | const ResponsiveWidget({ 5 | required this.largeScreen, 6 | this.mediumScreen, 7 | this.smallScreen, 8 | Key? key, 9 | }) : super(key: key); 10 | 11 | final Widget largeScreen; 12 | final Widget? mediumScreen; 13 | final Widget? smallScreen; 14 | 15 | static bool isSmallScreen(BuildContext context) { 16 | return MediaQuery.sizeOf(context).width < 800; 17 | } 18 | 19 | static bool isLargeScreen(BuildContext context) { 20 | return MediaQuery.sizeOf(context).width > 1200; 21 | } 22 | 23 | static bool isMediumScreen(BuildContext context) { 24 | return MediaQuery.sizeOf(context).width >= 800 && 25 | MediaQuery.sizeOf(context).width <= 1200; 26 | } 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | return LayoutBuilder( 31 | builder: (context, constraints) { 32 | if (constraints.maxWidth > 1200) { 33 | return largeScreen; 34 | } else if (constraints.maxWidth <= 1200 && 35 | constraints.maxWidth >= 800) { 36 | return mediumScreen ?? largeScreen; 37 | } else { 38 | return smallScreen ?? largeScreen; 39 | } 40 | }, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/select_header_style.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show Axis; 2 | 3 | import '../../../../widgets/toolbar/base_toolbar.dart'; 4 | import '../../../documents/attribute.dart'; 5 | 6 | class QuillToolbarSelectHeaderStyleButtonExtraOptions 7 | extends QuillToolbarBaseButtonExtraOptions { 8 | const QuillToolbarSelectHeaderStyleButtonExtraOptions({ 9 | required super.controller, 10 | required super.context, 11 | required super.onPressed, 12 | }); 13 | } 14 | 15 | class QuillToolbarSelectHeaderStyleButtonsOptions 16 | extends QuillToolbarBaseButtonOptions< 17 | QuillToolbarSelectHeaderStyleButtonsOptions, 18 | QuillToolbarSelectHeaderStyleButtonExtraOptions> { 19 | const QuillToolbarSelectHeaderStyleButtonsOptions({ 20 | super.afterButtonPressed, 21 | super.childBuilder, 22 | super.controller, 23 | super.iconData, 24 | super.iconTheme, 25 | super.tooltip, 26 | this.axis, 27 | this.attributes = const [ 28 | Attribute.header, 29 | Attribute.h1, 30 | Attribute.h2, 31 | Attribute.h3, 32 | ], 33 | this.iconSize, 34 | }); 35 | 36 | final List attributes; 37 | 38 | /// By default we will the toolbar axis from [QuillToolbarConfigurations] 39 | final Axis? axis; 40 | final double? iconSize; 41 | } 42 | -------------------------------------------------------------------------------- /lib/src/widgets/toolbar/buttons/custom_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../models/themes/quill_icon_theme.dart'; 4 | import '../base_toolbar.dart'; 5 | 6 | class CustomButton extends StatelessWidget { 7 | const CustomButton({ 8 | required this.onPressed, 9 | required this.icon, 10 | this.iconColor, 11 | this.iconSize = kDefaultIconSize, 12 | this.iconTheme, 13 | this.afterButtonPressed, 14 | this.tooltip, 15 | super.key, 16 | }); 17 | 18 | final VoidCallback? onPressed; 19 | final IconData? icon; 20 | final Color? iconColor; 21 | final double iconSize; 22 | final QuillIconTheme? iconTheme; 23 | final VoidCallback? afterButtonPressed; 24 | final String? tooltip; 25 | 26 | @override 27 | Widget build(BuildContext context) { 28 | final theme = Theme.of(context); 29 | 30 | final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; 31 | return QuillToolbarIconButton( 32 | highlightElevation: 0, 33 | hoverElevation: 0, 34 | size: iconSize * kIconButtonFactor, 35 | icon: Icon(icon, size: iconSize, color: iconColor), 36 | tooltip: tooltip, 37 | borderRadius: iconTheme?.borderRadius ?? 2, 38 | onPressed: onPressed, 39 | afterPressed: afterButtonPressed, 40 | fillColor: iconTheme?.iconUnselectedFillColor ?? theme.canvasColor, 41 | ); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/color.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show Color; 2 | import './../../shared_configurations.dart' show QuillSharedConfigurations; 3 | 4 | import 'base.dart'; 5 | 6 | class QuillToolbarColorButtonExtraOptions 7 | extends QuillToolbarBaseButtonExtraOptions { 8 | const QuillToolbarColorButtonExtraOptions({ 9 | required super.controller, 10 | required super.context, 11 | required super.onPressed, 12 | required this.iconColor, 13 | required this.iconColorBackground, 14 | required this.fillColor, 15 | required this.fillColorBackground, 16 | }); 17 | 18 | final Color? iconColor; 19 | final Color? iconColorBackground; 20 | final Color fillColor; 21 | final Color fillColorBackground; 22 | } 23 | 24 | class QuillToolbarColorButtonOptions extends QuillToolbarBaseButtonOptions< 25 | QuillToolbarColorButtonOptions, QuillToolbarColorButtonExtraOptions> { 26 | const QuillToolbarColorButtonOptions({ 27 | this.dialogBarrierColor, 28 | this.iconSize, 29 | super.iconData, 30 | super.afterButtonPressed, 31 | super.childBuilder, 32 | super.controller, 33 | super.globalIconSize, 34 | super.iconTheme, 35 | super.tooltip, 36 | }); 37 | 38 | final double? iconSize; 39 | 40 | /// By default will use the default `dialogBarrierColor` from 41 | /// [QuillSharedConfigurations] 42 | final Color? dialogBarrierColor; 43 | } 44 | -------------------------------------------------------------------------------- /example/windows/runner/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "flutter_window.h" 6 | #include "run_loop.h" 7 | #include "utils.h" 8 | 9 | int APIENTRY wWinMain(_In_ HINSTANCE instance, _In_opt_ HINSTANCE prev, 10 | _In_ wchar_t *command_line, _In_ int show_command) { 11 | // Attach to console when present (e.g., 'flutter run') or create a 12 | // new console when running with a debugger. 13 | if (!::AttachConsole(ATTACH_PARENT_PROCESS) && ::IsDebuggerPresent()) { 14 | CreateAndAttachConsole(); 15 | } 16 | 17 | // Initialize COM, so that it is available for use in the library and/or 18 | // plugins. 19 | ::CoInitializeEx(nullptr, COINIT_APARTMENTTHREADED); 20 | 21 | RunLoop run_loop; 22 | 23 | flutter::DartProject project(L"data"); 24 | 25 | std::vector command_line_arguments = 26 | GetCommandLineArguments(); 27 | 28 | project.set_dart_entrypoint_arguments(std::move(command_line_arguments)); 29 | 30 | FlutterWindow window(&run_loop, project); 31 | Win32Window::Point origin(10, 10); 32 | Win32Window::Size size(1280, 720); 33 | if (!window.CreateAndShow(L"app", origin, size)) { 34 | return EXIT_FAILURE; 35 | } 36 | window.SetQuitOnClose(true); 37 | 38 | run_loop.Run(); 39 | 40 | ::CoUninitialize(); 41 | return EXIT_SUCCESS; 42 | } 43 | -------------------------------------------------------------------------------- /example/macos/Podfile: -------------------------------------------------------------------------------- 1 | platform :osx, '12.0' 2 | 3 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 4 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 5 | 6 | project 'Runner', { 7 | 'Debug' => :debug, 8 | 'Profile' => :release, 9 | 'Release' => :release, 10 | } 11 | 12 | def flutter_root 13 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) 14 | unless File.exist?(generated_xcode_build_settings_path) 15 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" 16 | end 17 | 18 | File.foreach(generated_xcode_build_settings_path) do |line| 19 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 20 | return matches[1].strip if matches 21 | end 22 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" 23 | end 24 | 25 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 26 | 27 | flutter_macos_podfile_setup 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | use_modular_headers! 32 | 33 | flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) 34 | end 35 | 36 | post_install do |installer| 37 | installer.pods_project.targets.each do |target| 38 | flutter_additional_macos_build_settings(target) 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | # platform :ios, '11.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | project 'Runner', { 8 | 'Debug' => :debug, 9 | 'Profile' => :release, 10 | 'Release' => :release, 11 | } 12 | 13 | def flutter_root 14 | generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) 15 | unless File.exist?(generated_xcode_build_settings_path) 16 | raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" 17 | end 18 | 19 | File.foreach(generated_xcode_build_settings_path) do |line| 20 | matches = line.match(/FLUTTER_ROOT\=(.*)/) 21 | return matches[1].strip if matches 22 | end 23 | raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" 24 | end 25 | 26 | require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) 27 | 28 | flutter_ios_podfile_setup 29 | 30 | target 'Runner' do 31 | use_frameworks! 32 | use_modular_headers! 33 | 34 | flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) 35 | end 36 | 37 | post_install do |installer| 38 | installer.pods_project.targets.each do |target| 39 | flutter_additional_ios_build_settings(target) 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Pull Request 2 | 3 | ## Description 4 | 5 | Provide a brief description of your changes. 6 | 7 | ## Issues 8 | 9 | 10 | Closes #IssueNumber 11 | (Replace "IssueNumber" with the actual issue number you are addressing.) 12 | 13 | ## Improvements 14 | 15 | 16 | 17 | - Improve code readability 18 | - Improve performance 19 | 20 | ## Features 21 | 22 | 23 | 24 | - Add a new feature 25 | - Allow to customize the widgets 26 | 27 | 28 | 29 | ## Additional notes 30 | 31 | 32 | ## Suggestions 33 | 34 | 35 | ## Checklist 36 | 37 | 38 | 39 | - [ ] I have added/updated relevant documentation 40 | - [ ] I have tested these changes locally. 41 | - [ ] I have followed the code style and guidelines. 42 | - [ ] I have updated `CHANGELOG.md` with my changes in the next section 43 | - [ ] I have run `dart format .`` on the project 44 | - [ ] I have run `dart fix --apply` on the project 45 | - [ ] I have run `flutter test` and `flutter analyze` and it passed successfully 46 | - [ ] I have run `./before-push.sh` and everything is fine 47 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/toggle_check_list.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/foundation.dart' show immutable; 2 | import 'package:flutter/widgets.dart' show Color; 3 | 4 | import '../../../documents/attribute.dart'; 5 | import '../../quill_configurations.dart'; 6 | 7 | class QuillToolbarToggleCheckListButtonExtraOptions 8 | extends QuillToolbarBaseButtonExtraOptions { 9 | const QuillToolbarToggleCheckListButtonExtraOptions({ 10 | required super.controller, 11 | required super.context, 12 | required super.onPressed, 13 | this.isToggled = false, 14 | }); 15 | final bool isToggled; 16 | } 17 | 18 | @immutable 19 | class QuillToolbarToggleCheckListButtonOptions 20 | extends QuillToolbarBaseButtonOptions< 21 | QuillToolbarToggleCheckListButtonOptions, 22 | QuillToolbarToggleCheckListButtonExtraOptions> { 23 | const QuillToolbarToggleCheckListButtonOptions({ 24 | this.iconSize, 25 | this.fillColor, 26 | this.attribute = Attribute.unchecked, 27 | this.isShouldRequestKeyboard = false, 28 | super.controller, 29 | super.iconTheme, 30 | super.tooltip, 31 | super.iconData, 32 | super.afterButtonPressed, 33 | super.childBuilder, 34 | }); 35 | 36 | final double? iconSize; 37 | 38 | final Color? fillColor; 39 | 40 | final Attribute attribute; 41 | 42 | /// Should we request the keyboard when you press the toggle check list button 43 | /// ? if true then we will request the keyboard, if false then we will not 44 | /// but I think you already know that 45 | final bool isShouldRequestKeyboard; 46 | } 47 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: app 2 | description: demo app 3 | publish_to: 'none' 4 | version: 1.0.0+1 5 | 6 | environment: 7 | sdk: '>=2.12.0 <3.0.0' 8 | 9 | dependencies: 10 | flutter: 11 | sdk: flutter 12 | universal_html: ^2.2.4 13 | 14 | cupertino_icons: ^1.0.6 15 | path_provider: ^2.1.1 16 | filesystem_picker: ^4.0.0 17 | file_picker: ^6.0.0 18 | flutter_quill: 19 | path: ../ 20 | flutter_quill_extensions: 21 | path: ../flutter_quill_extensions 22 | 23 | dependency_overrides: 24 | flutter_quill: 25 | path: ../ 26 | 27 | dev_dependencies: 28 | flutter_test: 29 | sdk: flutter 30 | 31 | flutter: 32 | 33 | uses-material-design: true 34 | assets: 35 | - assets/ 36 | 37 | fonts: 38 | - family: monospace 39 | fonts: 40 | - asset: assets/fonts/MonoSpace.ttf 41 | - family: serif 42 | fonts: 43 | - asset: assets/fonts/Serif.ttf 44 | - family: sans-serif 45 | fonts: 46 | - asset: assets/fonts/SansSerif.ttf 47 | - family: ibarra-real-nova 48 | fonts: 49 | - asset: assets/fonts/IbarraRealNova-Regular.ttf 50 | - family: square-peg 51 | fonts: 52 | - asset: assets/fonts/SquarePeg-Regular.ttf 53 | - family: nunito 54 | fonts: 55 | - asset: assets/fonts/Nunito-Regular.ttf 56 | - family: pacifico 57 | fonts: 58 | - asset: assets/fonts/Pacifico-Regular.ttf 59 | - family: roboto-mono 60 | fonts: 61 | - asset: assets/fonts/RobotoMono-Regular.ttf 62 | - family: SF-UI-Display 63 | fonts: 64 | - asset: assets/fonts/SF-Pro-Display-Regular.otf -------------------------------------------------------------------------------- /lib/flutter_quill.dart: -------------------------------------------------------------------------------- 1 | library flutter_quill; 2 | 3 | export 'src/models/config/quill_configurations.dart'; 4 | export 'src/models/config/toolbar/base_configurations.dart'; 5 | export 'src/models/documents/attribute.dart'; 6 | export 'src/models/documents/document.dart'; 7 | export 'src/models/documents/nodes/block.dart'; 8 | export 'src/models/documents/nodes/embeddable.dart'; 9 | export 'src/models/documents/nodes/leaf.dart'; 10 | export 'src/models/documents/nodes/line.dart'; 11 | export 'src/models/documents/nodes/node.dart'; 12 | export 'src/models/documents/style.dart'; 13 | export 'src/models/quill_delta.dart'; 14 | export 'src/models/structs/doc_change.dart'; 15 | export 'src/models/structs/image_url.dart'; 16 | export 'src/models/structs/link_dialog_action.dart'; 17 | export 'src/models/structs/offset_value.dart'; 18 | export 'src/models/structs/optional_size.dart'; 19 | export 'src/models/structs/vertical_spacing.dart'; 20 | export 'src/models/themes/quill_custom_button.dart'; 21 | export 'src/models/themes/quill_dialog_theme.dart'; 22 | export 'src/models/themes/quill_icon_theme.dart'; 23 | export 'src/utils/embeds.dart'; 24 | export 'src/utils/extensions/build_context.dart'; 25 | export 'src/widgets/controller.dart'; 26 | export 'src/widgets/default_styles.dart'; 27 | export 'src/widgets/editor/editor.dart'; 28 | export 'src/widgets/embeds.dart'; 29 | export 'src/widgets/link.dart' show LinkActionPickerDelegate, LinkMenuAction; 30 | export 'src/widgets/style_widgets/style_widgets.dart'; 31 | export 'src/widgets/toolbar/base_toolbar.dart'; 32 | export 'src/widgets/toolbar/toolbar.dart'; 33 | export 'src/widgets/utils/provider.dart'; 34 | -------------------------------------------------------------------------------- /lib/src/widgets/toolbar/buttons/quill_icon.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../../utils/widgets.dart'; 4 | 5 | class QuillToolbarIconButton extends StatelessWidget { 6 | const QuillToolbarIconButton({ 7 | required this.onPressed, 8 | this.afterPressed, 9 | this.icon, 10 | this.size = 40, 11 | this.fillColor, 12 | this.hoverElevation = 1, 13 | this.highlightElevation = 1, 14 | this.borderRadius = 2, 15 | this.tooltip, 16 | Key? key, 17 | }) : super(key: key); 18 | 19 | final VoidCallback? onPressed; 20 | final VoidCallback? afterPressed; 21 | final Widget? icon; 22 | final double size; 23 | final Color? fillColor; 24 | final double hoverElevation; 25 | final double highlightElevation; 26 | final double borderRadius; 27 | final String? tooltip; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | return ConstrainedBox( 32 | constraints: BoxConstraints.tightFor(width: size, height: size), 33 | child: UtilityWidgets.maybeTooltip( 34 | message: tooltip, 35 | child: RawMaterialButton( 36 | visualDensity: VisualDensity.compact, 37 | shape: RoundedRectangleBorder( 38 | borderRadius: BorderRadius.circular(borderRadius), 39 | ), 40 | fillColor: fillColor, 41 | elevation: 0, 42 | hoverElevation: hoverElevation, 43 | highlightElevation: hoverElevation, 44 | onPressed: () { 45 | onPressed?.call(); 46 | afterPressed?.call(); 47 | }, 48 | child: icon, 49 | ), 50 | ), 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /example/web/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | app 30 | 31 | 32 | 33 | 36 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "app_icon_16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "app_icon_32.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "app_icon_32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "app_icon_64.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "app_icon_128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "app_icon_256.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "app_icon_256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "app_icon_512.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "app_icon_512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "app_icon_1024.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/toolbar/formula_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_quill/flutter_quill.dart'; 3 | 4 | class FormulaButton extends StatelessWidget { 5 | const FormulaButton({ 6 | required this.icon, 7 | required this.controller, 8 | this.iconSize = kDefaultIconSize, 9 | this.fillColor, 10 | this.iconTheme, 11 | this.dialogTheme, 12 | this.tooltip, 13 | Key? key, 14 | }) : super(key: key); 15 | 16 | final IconData icon; 17 | 18 | final double iconSize; 19 | 20 | final Color? fillColor; 21 | 22 | final QuillController controller; 23 | 24 | final QuillIconTheme? iconTheme; 25 | 26 | final QuillDialogTheme? dialogTheme; 27 | final String? tooltip; 28 | 29 | @override 30 | Widget build(BuildContext context) { 31 | final theme = Theme.of(context); 32 | 33 | final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; 34 | final iconFillColor = 35 | iconTheme?.iconUnselectedFillColor ?? (fillColor ?? theme.canvasColor); 36 | 37 | return QuillToolbarIconButton( 38 | icon: Icon(icon, size: iconSize, color: iconColor), 39 | tooltip: tooltip, 40 | highlightElevation: 0, 41 | hoverElevation: 0, 42 | size: iconSize * 1.77, 43 | fillColor: iconFillColor, 44 | borderRadius: iconTheme?.borderRadius ?? 2, 45 | onPressed: () => _onPressedHandler(context), 46 | ); 47 | } 48 | 49 | Future _onPressedHandler(BuildContext context) async { 50 | final index = controller.selection.baseOffset; 51 | final length = controller.selection.extentOffset - index; 52 | 53 | controller.replaceText(index, length, BlockEmbed.formula(''), null); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /lib/src/models/config/shared_configurations.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/material.dart' show Color, Colors, Locale; 3 | 4 | import './editor/configurations.dart' show QuillEditorConfigurations; 5 | import './toolbar/configurations.dart' show QuillToolbarConfigurations; 6 | import '../themes/quill_dialog_theme.dart'; 7 | import 'others/animations.dart'; 8 | 9 | export './others/animations.dart'; 10 | 11 | /// The shared configurations between [QuillEditorConfigurations] and 12 | /// [QuillToolbarConfigurations] so we don't duplicate things 13 | class QuillSharedConfigurations extends Equatable { 14 | const QuillSharedConfigurations({ 15 | this.dialogBarrierColor = Colors.black54, 16 | this.dialogTheme, 17 | this.locale, 18 | this.animationConfigurations = const QuillAnimationConfigurations( 19 | checkBoxPointItem: false, 20 | ), 21 | }); 22 | 23 | // This is just example or showcase of this major update to make the library 24 | // more maintanable, flexible, and customizable 25 | /// The barrier color of the shown dialogs 26 | final Color dialogBarrierColor; 27 | 28 | /// The default dialog theme for all the dialogs for quill editor and 29 | /// quill toolbar 30 | final QuillDialogTheme? dialogTheme; 31 | 32 | /// The locale to use for the editor and toolbar, defaults to system locale 33 | /// More https://github.com/singerdmx/flutter-quill#translation 34 | final Locale? locale; 35 | 36 | /// To configure which animations you want to be enabled 37 | final QuillAnimationConfigurations animationConfigurations; 38 | 39 | @override 40 | List get props => [ 41 | dialogBarrierColor, 42 | dialogTheme, 43 | locale, 44 | animationConfigurations, 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Miscellaneous 2 | *.class 3 | *.log 4 | *.pyc 5 | *.swp 6 | .DS_Store 7 | .atom/ 8 | .buildlog/ 9 | .history 10 | .svn/ 11 | 12 | # IntelliJ related 13 | *.iml 14 | *.ipr 15 | *.iws 16 | .idea/ 17 | 18 | # The .vscode folder contains launch configuration and tasks you configure in 19 | # VS Code which you may wish to be included in version control, so this line 20 | # is commented out by default. 21 | .vscode/ 22 | 23 | # Flutter/Dart/Pub related 24 | **/doc/api/ 25 | .dart_tool/ 26 | .flutter-plugins 27 | .flutter-plugins-dependencies 28 | .packages 29 | .pub-cache/ 30 | .pub/ 31 | build/ 32 | coverage/ 33 | 34 | # Android related 35 | **/android/**/gradle-wrapper.jar 36 | **/android/.gradle 37 | **/android/captures/ 38 | **/android/gradlew 39 | **/android/gradlew.bat 40 | **/android/local.properties 41 | **/android/**/GeneratedPluginRegistrant.java 42 | 43 | # iOS/XCode related 44 | **/ios/**/*.mode1v3 45 | **/ios/**/*.mode2v3 46 | **/ios/**/*.moved-aside 47 | **/ios/**/*.pbxuser 48 | **/ios/**/*.perspectivev3 49 | **/ios/**/*sync/ 50 | **/ios/**/.sconsign.dblite 51 | **/ios/**/.tags* 52 | **/ios/**/.vagrant/ 53 | **/ios/**/DerivedData/ 54 | **/ios/**/Icon? 55 | **/ios/**/Pods/ 56 | **/ios/**/.symlinks/ 57 | **/ios/**/profile 58 | **/ios/**/xcuserdata 59 | **/ios/.generated/ 60 | **/ios/Flutter/App.framework 61 | **/ios/Flutter/Flutter.framework 62 | **/ios/Flutter/Flutter.podspec 63 | **/ios/Flutter/Generated.xcconfig 64 | **/ios/Flutter/app.flx 65 | **/ios/Flutter/app.zip 66 | **/ios/Flutter/flutter_assets/ 67 | **/ios/Flutter/flutter_export_environment.sh 68 | **/ios/ServiceDefinitions.json 69 | **/ios/Runner/GeneratedPluginRegistrant.* 70 | example/ios/Podfile.lock 71 | 72 | # Exceptions to above rules. 73 | !**/ios/**/default.mode1v3 74 | !**/ios/**/default.mode2v3 75 | !**/ios/**/default.pbxuser 76 | !**/ios/**/default.perspectivev3 77 | pubspec.lock 78 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | app 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(FLUTTER_BUILD_NAME) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(FLUTTER_BUILD_NUMBER) 23 | NSPhotoLibraryUsageDescription 24 | Need to save image 25 | LSRequiresIPhoneOS 26 | 27 | UILaunchStoryboardName 28 | LaunchScreen 29 | UIMainStoryboardFile 30 | Main 31 | UISupportedInterfaceOrientations 32 | 33 | UIInterfaceOrientationPortrait 34 | UIInterfaceOrientationLandscapeLeft 35 | UIInterfaceOrientationLandscapeRight 36 | 37 | UISupportedInterfaceOrientations~ipad 38 | 39 | UIInterfaceOrientationPortrait 40 | UIInterfaceOrientationPortraitUpsideDown 41 | UIInterfaceOrientationLandscapeLeft 42 | UIInterfaceOrientationLandscapeRight 43 | 44 | UIViewControllerBasedStatusBarAppearance 45 | 46 | CADisableMinimumFrameDurationOnPhone 47 | 48 | UIApplicationSupportsIndirectInputEvents 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /example/windows/runner/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | 10 | void CreateAndAttachConsole() { 11 | if (::AllocConsole()) { 12 | FILE *unused; 13 | if (freopen_s(&unused, "CONOUT$", "w", stdout)) { 14 | _dup2(_fileno(stdout), 1); 15 | } 16 | if (freopen_s(&unused, "CONOUT$", "w", stderr)) { 17 | _dup2(_fileno(stdout), 2); 18 | } 19 | std::ios::sync_with_stdio(); 20 | FlutterDesktopResyncOutputStreams(); 21 | } 22 | } 23 | 24 | std::vector GetCommandLineArguments() { 25 | // Convert the UTF-16 command line arguments to UTF-8 for the Engine to use. 26 | int argc; 27 | wchar_t** argv = ::CommandLineToArgvW(::GetCommandLineW(), &argc); 28 | if (argv == nullptr) { 29 | return std::vector(); 30 | } 31 | 32 | std::vector command_line_arguments; 33 | 34 | // Skip the first argument as it's the binary name. 35 | for (int i = 1; i < argc; i++) { 36 | command_line_arguments.push_back(Utf8FromUtf16(argv[i])); 37 | } 38 | 39 | ::LocalFree(argv); 40 | 41 | return command_line_arguments; 42 | } 43 | 44 | std::string Utf8FromUtf16(const wchar_t* utf16_string) { 45 | if (utf16_string == nullptr) { 46 | return std::string(); 47 | } 48 | int target_length = ::WideCharToMultiByte( 49 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 50 | -1, nullptr, 0, nullptr, nullptr); 51 | if (target_length == 0) { 52 | return std::string(); 53 | } 54 | std::string utf8_string; 55 | utf8_string.resize(target_length); 56 | int converted_length = ::WideCharToMultiByte( 57 | CP_UTF8, WC_ERR_INVALID_CHARS, utf16_string, 58 | -1, utf8_string.data(), 59 | target_length, nullptr, nullptr); 60 | if (converted_length == 0) { 61 | return std::string(); 62 | } 63 | return utf8_string; 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/search.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show Color; 2 | 3 | import '../../../../../flutter_quill.dart'; 4 | 5 | class QuillToolbarSearchButtonExtraOptions 6 | extends QuillToolbarBaseButtonExtraOptions { 7 | const QuillToolbarSearchButtonExtraOptions({ 8 | required super.controller, 9 | required super.context, 10 | required super.onPressed, 11 | }); 12 | } 13 | 14 | class QuillToolbarSearchButtonOptions extends QuillToolbarBaseButtonOptions { 15 | const QuillToolbarSearchButtonOptions({ 16 | super.iconData, 17 | super.controller, 18 | super.childBuilder, 19 | super.tooltip, 20 | super.afterButtonPressed, 21 | super.iconTheme, 22 | this.dialogTheme, 23 | this.iconSize, 24 | this.dialogBarrierColor, 25 | this.fillColor, 26 | this.customOnPressedCallback, 27 | }); 28 | 29 | final QuillDialogTheme? dialogTheme; 30 | final double? iconSize; 31 | 32 | /// By default will be [dialogBarrierColor] from [QuillSharedConfigurations] 33 | final Color? dialogBarrierColor; 34 | 35 | final Color? fillColor; 36 | 37 | /// By default we will show simple search dialog ui 38 | /// you can pass value to this callback to change this 39 | final QuillToolbarSearchButtomOnPressedCallback? customOnPressedCallback; 40 | } 41 | 42 | typedef QuillToolbarSearchButtomOnPressedCallback = Future Function( 43 | QuillController controller, 44 | ); 45 | 46 | // typedef QuillToolbarSearchButtonFindTextCallback = List Function({ 47 | // required int index, 48 | // required String text, 49 | // required QuillController controller, 50 | // required List offsets, 51 | // required bool wholeWord, 52 | // required bool caseSensitive, 53 | // bool moveToPosition, 54 | // }); 55 | 56 | // typedef QuillToolbarSearchButtonMoveToPositionCallback = void Function({ 57 | // required int index, 58 | // required String text, 59 | // required QuillController controller, 60 | // required List offsets, 61 | // }); 62 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/buttons/select_alignment.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/widgets.dart' show IconData, immutable; 2 | import 'base.dart'; 3 | 4 | class QuillToolbarSelectAlignmentButtonExtraOptions 5 | extends QuillToolbarBaseButtonExtraOptions { 6 | const QuillToolbarSelectAlignmentButtonExtraOptions({ 7 | required super.controller, 8 | required super.context, 9 | required super.onPressed, 10 | }); 11 | } 12 | 13 | class QuillToolbarSelectAlignmentButtonOptions 14 | extends QuillToolbarBaseButtonOptions< 15 | QuillToolbarSelectAlignmentButtonOptions, 16 | QuillToolbarBaseButtonExtraOptions> { 17 | const QuillToolbarSelectAlignmentButtonOptions({ 18 | this.iconsData, 19 | this.tooltips, 20 | this.iconSize, 21 | super.afterButtonPressed, 22 | super.childBuilder, 23 | super.controller, 24 | super.iconTheme, 25 | }); 26 | final double? iconSize; 27 | 28 | /// Default to 29 | /// const QuillToolbarSelectAlignmentValues( 30 | /// leftAlignment: Icons.format_align_left, 31 | /// centerAlignment: Icons.format_align_center, 32 | /// rightAlignment: Icons.format_align_right, 33 | /// justifyAlignment: Icons.format_align_justify, 34 | /// ) 35 | final QuillSelectAlignmentValues? iconsData; 36 | 37 | /// By default will use the localized tooltips 38 | final QuillSelectAlignmentValues? tooltips; 39 | } 40 | 41 | /// A helper class which hold all the values for the alignments of the 42 | /// [QuillToolbarSelectAlignmentButtonOptions] 43 | /// it's not really related to the toolbar so we called it just Quill without 44 | /// toolbar but the name might change in the future 45 | @immutable 46 | class QuillSelectAlignmentValues { 47 | const QuillSelectAlignmentValues({ 48 | required this.leftAlignment, 49 | required this.centerAlignment, 50 | required this.rightAlignment, 51 | required this.justifyAlignment, 52 | }); 53 | 54 | final T leftAlignment; 55 | final T centerAlignment; 56 | final T rightAlignment; 57 | final T justifyAlignment; 58 | } 59 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/embed_types.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show File; 2 | import 'dart:typed_data'; 3 | 4 | import 'package:flutter/material.dart' 5 | show ImageErrorWidgetBuilder, BuildContext, ImageProvider; 6 | 7 | typedef OnImagePickCallback = Future Function(File file); 8 | typedef OnVideoPickCallback = Future Function(File file); 9 | typedef FilePickImpl = Future Function(BuildContext context); 10 | typedef WebImagePickImpl = Future Function( 11 | OnImagePickCallback onImagePickCallback); 12 | typedef WebVideoPickImpl = Future Function( 13 | OnVideoPickCallback onImagePickCallback); 14 | typedef MediaPickSettingSelector = Future Function( 15 | BuildContext context); 16 | 17 | enum MediaPickSetting { 18 | Gallery, 19 | Link, 20 | Camera, 21 | Video, 22 | } 23 | 24 | typedef MediaFileUrl = String; 25 | typedef MediaFilePicker = Future Function(QuillMediaType mediaType); 26 | typedef MediaPickedCallback = Future Function(QuillFile file); 27 | 28 | enum QuillMediaType { image, video } 29 | 30 | extension QuillMediaTypeX on QuillMediaType { 31 | bool get isImage => this == QuillMediaType.image; 32 | bool get isVideo => this == QuillMediaType.video; 33 | } 34 | 35 | /// Represents a file data which returned by file picker. 36 | class QuillFile { 37 | QuillFile({ 38 | required this.name, 39 | this.path = '', 40 | Uint8List? bytes, 41 | }) : assert(name.isNotEmpty), 42 | bytes = bytes ?? Uint8List(0); 43 | 44 | final String name; 45 | final String path; 46 | final Uint8List bytes; 47 | } 48 | 49 | typedef ImageEmbedBuilderWillRemoveCallback = Future Function( 50 | File imageFile, 51 | ); 52 | 53 | typedef ImageEmbedBuilderOnRemovedCallback = Future Function( 54 | File imageFile, 55 | ); 56 | 57 | typedef ImageEmbedBuilderProviderBuilder = ImageProvider Function( 58 | String imageUrl, 59 | // {required bool isLocalImage} 60 | ); 61 | 62 | typedef ImageEmbedBuilderErrorWidgetBuilder = ImageErrorWidgetBuilder; 63 | -------------------------------------------------------------------------------- /example/android/app/build.gradle: -------------------------------------------------------------------------------- 1 | def localProperties = new Properties() 2 | def localPropertiesFile = rootProject.file('local.properties') 3 | if (localPropertiesFile.exists()) { 4 | localPropertiesFile.withReader('UTF-8') { reader -> 5 | localProperties.load(reader) 6 | } 7 | } 8 | 9 | def flutterRoot = localProperties.getProperty('flutter.sdk') 10 | if (flutterRoot == null) { 11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") 12 | } 13 | 14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode') 15 | if (flutterVersionCode == null) { 16 | flutterVersionCode = '1' 17 | } 18 | 19 | def flutterVersionName = localProperties.getProperty('flutter.versionName') 20 | if (flutterVersionName == null) { 21 | flutterVersionName = '1.0' 22 | } 23 | 24 | apply plugin: 'com.android.application' 25 | apply plugin: 'kotlin-android' 26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 27 | 28 | android { 29 | compileSdkVersion flutter.compileSdkVersion 30 | 31 | compileOptions { 32 | sourceCompatibility JavaVersion.VERSION_1_8 33 | targetCompatibility JavaVersion.VERSION_1_8 34 | } 35 | 36 | kotlinOptions { 37 | jvmTarget = '1.8' 38 | } 39 | 40 | sourceSets { 41 | main.java.srcDirs += 'src/main/kotlin' 42 | } 43 | 44 | defaultConfig { 45 | applicationId "com.example.app" 46 | minSdkVersion 21 47 | targetSdkVersion flutter.targetSdkVersion 48 | versionCode flutterVersionCode.toInteger() 49 | versionName flutterVersionName 50 | // Multidex is not required for api level 21 51 | } 52 | 53 | buildTypes { 54 | release { 55 | // Signing with the debug keys for now, so `flutter run --release` works. 56 | signingConfig signingConfigs.debug 57 | } 58 | } 59 | } 60 | 61 | flutter { 62 | source '../..' 63 | } 64 | 65 | dependencies { 66 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 67 | } 68 | -------------------------------------------------------------------------------- /lib/src/models/documents/nodes/embeddable.dart: -------------------------------------------------------------------------------- 1 | import 'dart:convert'; 2 | 3 | /// An object which can be embedded into a Quill document. 4 | /// 5 | /// See also: 6 | /// 7 | /// * [BlockEmbed] which represents a block embed. 8 | class Embeddable { 9 | const Embeddable(this.type, this.data); 10 | 11 | /// The type of this object. 12 | final String type; 13 | 14 | /// The data payload of this object. 15 | final dynamic data; 16 | 17 | Map toJson() { 18 | return {type: data}; 19 | } 20 | 21 | static Embeddable fromJson(Map json) { 22 | final m = Map.from(json); 23 | assert(m.length == 1, 'Embeddable map must only have one key'); 24 | 25 | return Embeddable(m.keys.first, m.values.first); 26 | } 27 | } 28 | 29 | /// There are two built-in embed types supported by Quill documents, however 30 | /// the document model itself does not make any assumptions about the types 31 | /// of embedded objects and allows users to define their own types. 32 | class BlockEmbed extends Embeddable { 33 | const BlockEmbed(String type, String data) : super(type, data); 34 | 35 | static const String imageType = 'image'; 36 | static BlockEmbed image(String imageUrl) => BlockEmbed(imageType, imageUrl); 37 | 38 | static const String videoType = 'video'; 39 | static BlockEmbed video(String videoUrl) => BlockEmbed(videoType, videoUrl); 40 | 41 | static const String formulaType = 'formula'; 42 | static BlockEmbed formula(String formula) => BlockEmbed(formulaType, formula); 43 | 44 | static const String customType = 'custom'; 45 | static BlockEmbed custom(CustomBlockEmbed customBlock) => 46 | BlockEmbed(customType, customBlock.toJsonString()); 47 | } 48 | 49 | class CustomBlockEmbed extends BlockEmbed { 50 | const CustomBlockEmbed(String type, String data) : super(type, data); 51 | 52 | String toJsonString() => jsonEncode(toJson()); 53 | 54 | static CustomBlockEmbed fromJsonString(String data) { 55 | final embeddable = Embeddable.fromJson(jsonDecode(data)); 56 | return CustomBlockEmbed(embeddable.type, embeddable.data); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /lib/src/models/documents/nodes/block.dart: -------------------------------------------------------------------------------- 1 | import '../../quill_delta.dart'; 2 | import 'container.dart'; 3 | import 'line.dart'; 4 | import 'node.dart'; 5 | 6 | /// Represents a group of adjacent [Line]s with the same block style. 7 | /// 8 | /// Block elements are: 9 | /// - Blockquote 10 | /// - Header 11 | /// - Indent 12 | /// - List 13 | /// - Text Alignment 14 | /// - Text Direction 15 | /// - Code Block 16 | class Block extends Container { 17 | /// Creates new unmounted [Block]. 18 | @override 19 | Node newInstance() => Block(); 20 | 21 | @override 22 | Line get defaultChild => Line(); 23 | 24 | @override 25 | Delta toDelta() { 26 | // Line nodes take care of incorporating block style into their delta. 27 | return children 28 | .map((child) => child.toDelta()) 29 | .fold(Delta(), (a, b) => a.concat(b)); 30 | } 31 | 32 | @override 33 | void adjust() { 34 | if (isEmpty) { 35 | final sibling = previous; 36 | unlink(); 37 | if (sibling != null) { 38 | sibling.adjust(); 39 | } 40 | return; 41 | } 42 | 43 | var block = this; 44 | final prev = block.previous; 45 | // merging it with previous block if style is the same 46 | if (!block.isFirst && 47 | block.previous is Block && 48 | prev!.style == block.style) { 49 | block 50 | ..moveChildToNewParent(prev as Container?) 51 | ..unlink(); 52 | block = prev as Block; 53 | } 54 | final next = block.next; 55 | // merging it with next block if style is the same 56 | if (!block.isLast && block.next is Block && next!.style == block.style) { 57 | (next as Block).moveChildToNewParent(block); 58 | next.unlink(); 59 | } 60 | } 61 | 62 | @override 63 | String toString() { 64 | final block = style.attributes.toString(); 65 | final buffer = StringBuffer('§ {$block}\n'); 66 | for (final child in children) { 67 | final tree = child.isLast ? '└' : '├'; 68 | buffer.write(' $tree $child'); 69 | if (!child.isLast) buffer.writeln(); 70 | } 71 | return buffer.toString(); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /lib/src/utils/platform.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show Platform; 2 | 3 | import 'package:device_info_plus/device_info_plus.dart'; 4 | import 'package:flutter/foundation.dart' 5 | show kIsWeb, TargetPlatform, defaultTargetPlatform; 6 | 7 | bool isWeb() { 8 | return kIsWeb; 9 | } 10 | 11 | bool isMobile([TargetPlatform? targetPlatform]) { 12 | if (isWeb()) return false; 13 | targetPlatform ??= defaultTargetPlatform; 14 | return {TargetPlatform.iOS, TargetPlatform.android}.contains(targetPlatform); 15 | } 16 | 17 | bool isDesktop([TargetPlatform? targetPlatform]) { 18 | if (isWeb()) return false; 19 | targetPlatform ??= defaultTargetPlatform; 20 | return {TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows} 21 | .contains(targetPlatform); 22 | } 23 | 24 | bool isKeyboardOS([TargetPlatform? targetPlatform]) { 25 | targetPlatform ??= defaultTargetPlatform; 26 | return isDesktop(targetPlatform) || targetPlatform == TargetPlatform.fuchsia; 27 | } 28 | 29 | bool isAppleOS([TargetPlatform? targetPlatform]) { 30 | if (isWeb()) return false; 31 | targetPlatform ??= defaultTargetPlatform; 32 | return { 33 | TargetPlatform.macOS, 34 | TargetPlatform.iOS, 35 | }.contains(targetPlatform); 36 | } 37 | 38 | bool isMacOS([TargetPlatform? targetPlatform]) { 39 | if (isWeb()) return false; 40 | targetPlatform ??= defaultTargetPlatform; 41 | return TargetPlatform.macOS == targetPlatform; 42 | } 43 | 44 | bool isIOS([TargetPlatform? targetPlatform]) { 45 | if (isWeb()) return false; 46 | targetPlatform ??= defaultTargetPlatform; 47 | return TargetPlatform.iOS == targetPlatform; 48 | } 49 | 50 | bool isAndroid([TargetPlatform? targetPlatform]) { 51 | if (isWeb()) return false; 52 | targetPlatform ??= defaultTargetPlatform; 53 | return TargetPlatform.android == targetPlatform; 54 | } 55 | 56 | bool isFlutterTest() { 57 | if (isWeb()) return false; 58 | return Platform.environment.containsKey('FLUTTER_TEST'); 59 | } 60 | 61 | Future isIOSSimulator() async { 62 | if (!isAppleOS()) { 63 | return false; 64 | } 65 | 66 | final deviceInfo = DeviceInfoPlugin(); 67 | 68 | final osInfo = await deviceInfo.deviceInfo; 69 | 70 | if (osInfo is IosDeviceInfo) { 71 | final iosInfo = osInfo; 72 | return !iosInfo.isPhysicalDevice; 73 | } 74 | return false; 75 | } 76 | -------------------------------------------------------------------------------- /example/windows/runner/flutter_window.cpp: -------------------------------------------------------------------------------- 1 | #include "flutter_window.h" 2 | 3 | #include 4 | 5 | #include "flutter/generated_plugin_registrant.h" 6 | 7 | FlutterWindow::FlutterWindow(RunLoop* run_loop, 8 | const flutter::DartProject& project) 9 | : run_loop_(run_loop), project_(project) {} 10 | 11 | FlutterWindow::~FlutterWindow() {} 12 | 13 | bool FlutterWindow::OnCreate() { 14 | if (!Win32Window::OnCreate()) { 15 | return false; 16 | } 17 | 18 | RECT frame = GetClientArea(); 19 | 20 | // The size here must match the window dimensions to avoid unnecessary surface 21 | // creation / destruction in the startup path. 22 | flutter_controller_ = std::make_unique( 23 | frame.right - frame.left, frame.bottom - frame.top, project_); 24 | // Ensure that basic setup of the controller was successful. 25 | if (!flutter_controller_->engine() || !flutter_controller_->view()) { 26 | return false; 27 | } 28 | RegisterPlugins(flutter_controller_->engine()); 29 | run_loop_->RegisterFlutterInstance(flutter_controller_->engine()); 30 | SetChildContent(flutter_controller_->view()->GetNativeWindow()); 31 | return true; 32 | } 33 | 34 | void FlutterWindow::OnDestroy() { 35 | if (flutter_controller_) { 36 | run_loop_->UnregisterFlutterInstance(flutter_controller_->engine()); 37 | flutter_controller_ = nullptr; 38 | } 39 | 40 | Win32Window::OnDestroy(); 41 | } 42 | 43 | LRESULT 44 | FlutterWindow::MessageHandler(HWND hwnd, UINT const message, 45 | WPARAM const wparam, 46 | LPARAM const lparam) noexcept { 47 | // Give Flutter, including plugins, an opporutunity to handle window messages. 48 | if (flutter_controller_) { 49 | std::optional result = 50 | flutter_controller_->HandleTopLevelWindowProc(hwnd, message, wparam, 51 | lparam); 52 | if (result) { 53 | return *result; 54 | } 55 | } 56 | 57 | switch (message) { 58 | case WM_FONTCHANGE: 59 | flutter_controller_->engine()->ReloadSystemFonts(); 60 | break; 61 | } 62 | 63 | return Win32Window::MessageHandler(hwnd, message, wparam, lparam); 64 | } 65 | -------------------------------------------------------------------------------- /lib/src/models/config/toolbar/base_configurations.dart: -------------------------------------------------------------------------------- 1 | import 'package:equatable/equatable.dart'; 2 | import 'package:flutter/widgets.dart' 3 | show Axis, Color, Decoration, WrapAlignment, WrapCrossAlignment, immutable; 4 | 5 | import '../../../widgets/toolbar/base_toolbar.dart'; 6 | import '../../structs/link_dialog_action.dart'; 7 | import '../../themes/quill_custom_button.dart'; 8 | 9 | @immutable 10 | class QuillBaseToolbarConfigurations extends Equatable { 11 | const QuillBaseToolbarConfigurations({ 12 | required this.childrenBuilder, 13 | this.axis = Axis.horizontal, 14 | this.toolbarSize = kDefaultIconSize * 2, 15 | this.toolbarSectionSpacing = kToolbarSectionSpacing, 16 | this.toolbarIconAlignment = WrapAlignment.center, 17 | this.toolbarIconCrossAlignment = WrapCrossAlignment.center, 18 | this.color, 19 | this.customButtons = const [], 20 | this.sectionDividerColor, 21 | this.sectionDividerSpace, 22 | this.linkDialogAction, 23 | this.multiRowsDisplay = true, 24 | this.decoration, 25 | }); 26 | 27 | final QuillBaseToolbarChildrenBuilder childrenBuilder; 28 | final Axis axis; 29 | final double toolbarSectionSpacing; 30 | final WrapAlignment toolbarIconAlignment; 31 | final WrapCrossAlignment toolbarIconCrossAlignment; 32 | final double toolbarSize; 33 | 34 | // Overrides the action in the _LinkDialog widget 35 | final LinkDialogAction? linkDialogAction; 36 | 37 | /// The color of the toolbar. 38 | /// 39 | /// Defaults to [ThemeData.canvasColor] of the current [Theme] if no color 40 | /// is given. 41 | final Color? color; 42 | 43 | /// List of custom buttons 44 | final List customButtons; 45 | 46 | /// The color to use when painting the toolbar section divider. 47 | /// 48 | /// If this is null, then the [DividerThemeData.color] is used. If that is 49 | /// also null, then [ThemeData.dividerColor] is used. 50 | final Color? sectionDividerColor; 51 | 52 | /// The space occupied by toolbar section divider. 53 | final double? sectionDividerSpace; 54 | 55 | /// If you want the toolbar to not be a multiple rows pass false 56 | final bool multiRowsDisplay; 57 | 58 | /// The decoration to use for the toolbar. 59 | final Decoration? decoration; 60 | 61 | @override 62 | List get props => []; 63 | } 64 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 15 | 23 | 27 | 31 | 32 | 33 | 34 | 35 | 36 | 38 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/widgets/youtube_video_app.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/gestures.dart'; 2 | import 'package:flutter/material.dart'; 3 | import 'package:flutter_quill/flutter_quill.dart'; 4 | import 'package:url_launcher/url_launcher.dart'; 5 | import 'package:youtube_player_flutter/youtube_player_flutter.dart'; 6 | 7 | class YoutubeVideoApp extends StatefulWidget { 8 | const YoutubeVideoApp( 9 | {required this.videoUrl, required this.context, required this.readOnly}); 10 | 11 | final String videoUrl; 12 | final BuildContext context; 13 | final bool readOnly; 14 | 15 | @override 16 | _YoutubeVideoAppState createState() => _YoutubeVideoAppState(); 17 | } 18 | 19 | class _YoutubeVideoAppState extends State { 20 | var _youtubeController; 21 | 22 | @override 23 | void initState() { 24 | super.initState(); 25 | final videoId = YoutubePlayer.convertUrlToId(widget.videoUrl); 26 | if (videoId != null) { 27 | _youtubeController = YoutubePlayerController( 28 | initialVideoId: videoId, 29 | flags: const YoutubePlayerFlags( 30 | autoPlay: false, 31 | ), 32 | ); 33 | } 34 | } 35 | 36 | @override 37 | Widget build(BuildContext context) { 38 | final defaultStyles = DefaultStyles.getInstance(context); 39 | if (_youtubeController == null) { 40 | if (widget.readOnly) { 41 | return RichText( 42 | text: TextSpan( 43 | text: widget.videoUrl, 44 | style: defaultStyles.link, 45 | recognizer: TapGestureRecognizer() 46 | ..onTap = () => launchUrl(Uri.parse(widget.videoUrl))), 47 | ); 48 | } 49 | 50 | return RichText( 51 | text: TextSpan(text: widget.videoUrl, style: defaultStyles.link)); 52 | } 53 | 54 | return Container( 55 | height: 300, 56 | child: YoutubePlayerBuilder( 57 | player: YoutubePlayer( 58 | controller: _youtubeController, 59 | showVideoProgressIndicator: true, 60 | ), 61 | builder: (context, player) { 62 | return Column( 63 | children: [ 64 | // some widgets 65 | player, 66 | //some other widgets 67 | ], 68 | ); 69 | }, 70 | ), 71 | ); 72 | } 73 | 74 | @override 75 | void dispose() { 76 | super.dispose(); 77 | _youtubeController.dispose(); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /lib/src/test/widget_tester_extension.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | 4 | import '../widgets/editor/editor.dart'; 5 | import '../widgets/raw_editor/raw_editor.dart'; 6 | 7 | /// Extends 8 | extension QuillEnterText on WidgetTester { 9 | /// Give the QuillEditor widget specified by [finder] the focus. 10 | Future quillGiveFocus(Finder finder) { 11 | return TestAsyncUtils.guard(() async { 12 | final editor = state( 13 | find.descendant( 14 | of: finder, 15 | matching: 16 | find.byType(QuillEditor, skipOffstage: finder.skipOffstage), 17 | matchRoot: true), 18 | ); 19 | editor.widget.focusNode.requestFocus(); 20 | await pump(); 21 | expect(editor.widget.focusNode.hasFocus, isTrue); 22 | }); 23 | } 24 | 25 | /// Give the QuillEditor widget specified by [finder] the focus and update its 26 | /// editing value with [text], as if it had been provided by the onscreen 27 | /// keyboard. 28 | /// 29 | /// The widget specified by [finder] must be a [QuillEditor] or have a 30 | /// [QuillEditor] descendant. For example `find.byType(QuillEditor)`. 31 | Future quillEnterText(Finder finder, String text) async { 32 | return TestAsyncUtils.guard(() async { 33 | await quillGiveFocus(finder); 34 | await quillUpdateEditingValue(finder, text); 35 | await idle(); 36 | }); 37 | } 38 | 39 | /// Update the text editing value of the QuillEditor widget specified by 40 | /// [finder] with [text], as if it had been provided by the onscreen keyboard. 41 | /// 42 | /// The widget specified by [finder] must already have focus and be a 43 | /// [QuillEditor] or have a [QuillEditor] descendant. For example 44 | /// `find.byType(QuillEditor)`. 45 | Future quillUpdateEditingValue(Finder finder, String text) async { 46 | return TestAsyncUtils.guard(() async { 47 | final editor = state( 48 | find.descendant( 49 | of: finder, 50 | matching: find.byType(RawEditor, skipOffstage: finder.skipOffstage), 51 | matchRoot: true), 52 | ); 53 | testTextInput.updateEditingValue(TextEditingValue( 54 | text: text, 55 | selection: TextSelection.collapsed( 56 | offset: editor.textEditingValue.text.length))); 57 | await idle(); 58 | }); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /example/windows/runner/run_loop.cpp: -------------------------------------------------------------------------------- 1 | #include "run_loop.h" 2 | 3 | #include 4 | 5 | #include 6 | 7 | RunLoop::RunLoop() {} 8 | 9 | RunLoop::~RunLoop() {} 10 | 11 | void RunLoop::Run() { 12 | bool keep_running = true; 13 | TimePoint next_flutter_event_time = TimePoint::clock::now(); 14 | while (keep_running) { 15 | std::chrono::nanoseconds wait_duration = 16 | std::max(std::chrono::nanoseconds(0), 17 | next_flutter_event_time - TimePoint::clock::now()); 18 | ::MsgWaitForMultipleObjects( 19 | 0, nullptr, FALSE, static_cast(wait_duration.count() / 1000), 20 | QS_ALLINPUT); 21 | bool processed_events = false; 22 | MSG message; 23 | // All pending Windows messages must be processed; MsgWaitForMultipleObjects 24 | // won't return again for items left in the queue after PeekMessage. 25 | while (::PeekMessage(&message, nullptr, 0, 0, PM_REMOVE)) { 26 | processed_events = true; 27 | if (message.message == WM_QUIT) { 28 | keep_running = false; 29 | break; 30 | } 31 | ::TranslateMessage(&message); 32 | ::DispatchMessage(&message); 33 | // Allow Flutter to process messages each time a Windows message is 34 | // processed, to prevent starvation. 35 | next_flutter_event_time = 36 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 37 | } 38 | // If the PeekMessage loop didn't run, process Flutter messages. 39 | if (!processed_events) { 40 | next_flutter_event_time = 41 | std::min(next_flutter_event_time, ProcessFlutterMessages()); 42 | } 43 | } 44 | } 45 | 46 | void RunLoop::RegisterFlutterInstance( 47 | flutter::FlutterEngine* flutter_instance) { 48 | flutter_instances_.insert(flutter_instance); 49 | } 50 | 51 | void RunLoop::UnregisterFlutterInstance( 52 | flutter::FlutterEngine* flutter_instance) { 53 | flutter_instances_.erase(flutter_instance); 54 | } 55 | 56 | RunLoop::TimePoint RunLoop::ProcessFlutterMessages() { 57 | TimePoint next_event_time = TimePoint::max(); 58 | for (auto instance : flutter_instances_) { 59 | std::chrono::nanoseconds wait_duration = instance->ProcessMessages(); 60 | if (wait_duration != std::chrono::nanoseconds::max()) { 61 | next_event_time = 62 | std::min(next_event_time, TimePoint::clock::now() + wait_duration); 63 | } 64 | } 65 | return next_event_time; 66 | } 67 | -------------------------------------------------------------------------------- /example/ios/Runner/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/lib/pages/read_only_page.dart: -------------------------------------------------------------------------------- 1 | // ignore_for_file: avoid_redundant_argument_values 2 | 3 | import 'package:flutter/foundation.dart'; 4 | import 'package:flutter/material.dart'; 5 | import 'package:flutter_quill/extensions.dart'; 6 | import 'package:flutter_quill/flutter_quill.dart'; 7 | import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; 8 | 9 | import '../universal_ui/universal_ui.dart'; 10 | import '../widgets/demo_scaffold.dart'; 11 | 12 | class ReadOnlyPage extends StatefulWidget { 13 | @override 14 | _ReadOnlyPageState createState() => _ReadOnlyPageState(); 15 | } 16 | 17 | class _ReadOnlyPageState extends State { 18 | final FocusNode _focusNode = FocusNode(); 19 | 20 | bool _edit = false; 21 | 22 | @override 23 | Widget build(BuildContext context) { 24 | return DemoScaffold( 25 | documentFilename: isDesktop() 26 | ? 'assets/sample_data_nomedia.json' 27 | : 'sample_data_nomedia.json', 28 | builder: _buildContent, 29 | showToolbar: _edit == true, 30 | floatingActionButton: FloatingActionButton.extended( 31 | label: Text(_edit == true ? 'Done' : 'Edit'), 32 | onPressed: _toggleEdit, 33 | icon: Icon(_edit == true ? Icons.check : Icons.edit), 34 | ), 35 | ); 36 | } 37 | 38 | Widget _buildContent(BuildContext context, QuillController? controller) { 39 | var quillEditor = QuillEditor( 40 | configurations: QuillEditorConfigurations( 41 | expands: false, 42 | padding: EdgeInsets.zero, 43 | embedBuilders: FlutterQuillEmbeds.builders(), 44 | scrollable: true, 45 | autoFocus: true, 46 | ), 47 | scrollController: ScrollController(), 48 | focusNode: _focusNode, 49 | // readOnly: !_edit, 50 | ); 51 | if (kIsWeb) { 52 | quillEditor = QuillEditor( 53 | configurations: QuillEditorConfigurations( 54 | autoFocus: true, 55 | expands: false, 56 | padding: EdgeInsets.zero, 57 | embedBuilders: defaultEmbedBuildersWeb, 58 | scrollable: true, 59 | ), 60 | scrollController: ScrollController(), 61 | focusNode: _focusNode, 62 | ); 63 | } 64 | return Padding( 65 | padding: const EdgeInsets.all(8), 66 | child: Container( 67 | decoration: BoxDecoration( 68 | color: Colors.white, 69 | border: Border.all(color: Colors.grey.shade200), 70 | ), 71 | child: quillEditor, 72 | ), 73 | ); 74 | } 75 | 76 | void _toggleEdit() { 77 | setState(() { 78 | _edit = !_edit; 79 | }); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /flutter_quill_extensions/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.5.1 2 | 3 | - Provide a way to use custom image provider for the image widgets 4 | - Provide a way to handle different errors in image widgets 5 | - Two bug fixes related to pick the image and capture it using the camera 6 | - Add support for image resizing on desktop platforms when forced using the mobile context menu 7 | - Improve performance by reducing the number of widgets rebuilt by listening to media query for only the needed things, for example instead of using `MediaQuery.of(context).size`, now we are using `MediaQuery.sizeOf(context)` 8 | - Fix warrning "The platformViewRegistry getter is deprecated and will be removed in a future release. Please import it from dart:ui_web instead." 9 | - Add QuillImageUtilities class 10 | - Small improvemenets 11 | - Allow to use the mobile context menu on desktop by force using it 12 | - Add the resizing option to the forced mobile context menu 13 | - Add new custom style attrbuite for desktop and other platforms 14 | 15 | ## 0.5.0 16 | 17 | - Migrated from `gallery_saver` to `gal` for saving images 18 | - Added callbacks for greater control of editing images 19 | 20 | ## 0.4.1 21 | 22 | - Updated dependencies to support image_picker 1.0 23 | 24 | ## 0.4.0 25 | 26 | - Fix backspace around images [PR #1309](https://github.com/singerdmx/flutter-quill/pull/1309) 27 | - Feat/link regexp [PR #1329](https://github.com/singerdmx/flutter-quill/pull/1329) 28 | 29 | ## 0.3.4 30 | 31 | - Resolve deprecated method use in the `video_player` package 32 | 33 | ## 0.3.3 34 | 35 | - Fix a prototype bug which was bring by [PR #1230](https://github.com/singerdmx/flutter-quill/pull/1230#issuecomment-1560597099) 36 | 37 | ## 0.3.2 38 | 39 | - Updated dependencies to support intl 0.18 40 | 41 | ## 0.3.1 42 | 43 | - Image embedding tweaks 44 | - Add MediaButton which is intened to superseed the ImageButton and VideoButton. Only image selection is working. 45 | - Implement image insert for web (image as base64) 46 | 47 | ## 0.3.0 48 | 49 | - Added support for adding custom tooltips to toolbar buttons 50 | 51 | ## 0.2.0 52 | 53 | - Allow widgets to override widget span properties [b7951b0](https://github.com/singerdmx/flutter-quill/commit/b7951b02c9086ea42e7aad6d78e6c9b0297562e5) 54 | - Remove tuples [3e9452e](https://github.com/singerdmx/flutter-quill/commit/3e9452e675e8734ff50364c5f7b5d34088d5ff05) 55 | - Remove transparent color of ImageVideoUtils dialog [74544bd](https://github.com/singerdmx/flutter-quill/commit/74544bd945a9d212ca1e8d6b3053dbecee22b720) 56 | - Migrate to `youtube_player_flutter` from `youtube_player_flutter_quill` 57 | - Updates to forumla button [5228f38](https://github.com/singerdmx/flutter-quill/commit/5228f389ba6f37d61d445cfe138c19fcf8766d71) 58 | 59 | ## 0.1.0 60 | 61 | - Initial release 62 | -------------------------------------------------------------------------------- /lib/src/utils/string.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | 3 | import '../models/documents/attribute.dart'; 4 | 5 | Map parseKeyValuePairs(String s, Set targetKeys) { 6 | final result = {}; 7 | final pairs = s.split(';'); 8 | for (final pair in pairs) { 9 | final _index = pair.indexOf(':'); 10 | if (_index < 0) { 11 | continue; 12 | } 13 | final _key = pair.substring(0, _index).trim(); 14 | if (targetKeys.contains(_key)) { 15 | result[_key] = pair.substring(_index + 1).trim(); 16 | } 17 | } 18 | 19 | return result; 20 | } 21 | 22 | @Deprecated('Use replaceStyleStringWithSize instead') 23 | String replaceStyleString( 24 | String s, 25 | double width, 26 | double height, 27 | ) { 28 | return replaceStyleStringWithSize( 29 | s, 30 | width: width, 31 | height: height, 32 | isMobile: true, 33 | ); 34 | } 35 | 36 | String replaceStyleStringWithSize( 37 | String s, { 38 | required double width, 39 | required double height, 40 | required bool isMobile, 41 | }) { 42 | final result = {}; 43 | final pairs = s.split(';'); 44 | for (final pair in pairs) { 45 | final _index = pair.indexOf(':'); 46 | if (_index < 0) { 47 | continue; 48 | } 49 | final _key = pair.substring(0, _index).trim(); 50 | result[_key] = pair.substring(_index + 1).trim(); 51 | } 52 | 53 | if (isMobile) { 54 | result[Attribute.mobileWidth] = width.toString(); 55 | result[Attribute.mobileHeight] = height.toString(); 56 | } else { 57 | result[Attribute.width.key] = width.toString(); 58 | result[Attribute.height.key] = height.toString(); 59 | } 60 | final sb = StringBuffer(); 61 | for (final pair in result.entries) { 62 | sb 63 | ..write(pair.key) 64 | ..write(': ') 65 | ..write(pair.value) 66 | ..write('; '); 67 | } 68 | return sb.toString(); 69 | } 70 | 71 | Alignment getAlignment(String? s) { 72 | const _defaultAlignment = Alignment.center; 73 | if (s == null) { 74 | return _defaultAlignment; 75 | } 76 | 77 | final _index = [ 78 | 'topLeft', 79 | 'topCenter', 80 | 'topRight', 81 | 'centerLeft', 82 | 'center', 83 | 'centerRight', 84 | 'bottomLeft', 85 | 'bottomCenter', 86 | 'bottomRight' 87 | ].indexOf(s); 88 | if (_index < 0) { 89 | return _defaultAlignment; 90 | } 91 | 92 | return [ 93 | Alignment.topLeft, 94 | Alignment.topCenter, 95 | Alignment.topRight, 96 | Alignment.centerLeft, 97 | Alignment.center, 98 | Alignment.centerRight, 99 | Alignment.bottomLeft, 100 | Alignment.bottomCenter, 101 | Alignment.bottomRight 102 | ][_index]; 103 | } 104 | -------------------------------------------------------------------------------- /lib/src/widgets/keyboard_listener.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter/services.dart'; 3 | 4 | class QuillPressedKeys extends ChangeNotifier { 5 | static QuillPressedKeys of(BuildContext context) { 6 | final widget = 7 | context.dependOnInheritedWidgetOfExactType<_QuillPressedKeysAccess>(); 8 | return widget!.pressedKeys; 9 | } 10 | 11 | bool _metaPressed = false; 12 | bool _controlPressed = false; 13 | 14 | /// Whether meta key is currently pressed. 15 | bool get metaPressed => _metaPressed; 16 | 17 | /// Whether control key is currently pressed. 18 | bool get controlPressed => _controlPressed; 19 | 20 | void _updatePressedKeys(Set pressedKeys) { 21 | final meta = pressedKeys.contains(LogicalKeyboardKey.metaLeft) || 22 | pressedKeys.contains(LogicalKeyboardKey.metaRight); 23 | final control = pressedKeys.contains(LogicalKeyboardKey.controlLeft) || 24 | pressedKeys.contains(LogicalKeyboardKey.controlRight); 25 | if (_metaPressed != meta || _controlPressed != control) { 26 | _metaPressed = meta; 27 | _controlPressed = control; 28 | notifyListeners(); 29 | } 30 | } 31 | } 32 | 33 | class QuillKeyboardListener extends StatefulWidget { 34 | const QuillKeyboardListener({required this.child, Key? key}) 35 | : super(key: key); 36 | 37 | final Widget child; 38 | 39 | @override 40 | QuillKeyboardListenerState createState() => QuillKeyboardListenerState(); 41 | } 42 | 43 | class QuillKeyboardListenerState extends State { 44 | final QuillPressedKeys _pressedKeys = QuillPressedKeys(); 45 | 46 | bool _keyEvent(KeyEvent event) { 47 | _pressedKeys 48 | ._updatePressedKeys(HardwareKeyboard.instance.logicalKeysPressed); 49 | return false; 50 | } 51 | 52 | @override 53 | void initState() { 54 | super.initState(); 55 | HardwareKeyboard.instance.addHandler(_keyEvent); 56 | _pressedKeys 57 | ._updatePressedKeys(HardwareKeyboard.instance.logicalKeysPressed); 58 | } 59 | 60 | @override 61 | void dispose() { 62 | HardwareKeyboard.instance.removeHandler(_keyEvent); 63 | _pressedKeys.dispose(); 64 | super.dispose(); 65 | } 66 | 67 | @override 68 | Widget build(BuildContext context) { 69 | return _QuillPressedKeysAccess( 70 | pressedKeys: _pressedKeys, 71 | child: widget.child, 72 | ); 73 | } 74 | } 75 | 76 | class _QuillPressedKeysAccess extends InheritedWidget { 77 | const _QuillPressedKeysAccess({ 78 | required this.pressedKeys, 79 | required Widget child, 80 | Key? key, 81 | }) : super(key: key, child: child); 82 | 83 | final QuillPressedKeys pressedKeys; 84 | 85 | @override 86 | bool updateShouldNotify(covariant _QuillPressedKeysAccess oldWidget) { 87 | return oldWidget.pressedKeys != pressedKeys; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /lib/src/models/rules/rule.dart: -------------------------------------------------------------------------------- 1 | import '../documents/attribute.dart'; 2 | import '../documents/document.dart'; 3 | import '../quill_delta.dart'; 4 | import 'delete.dart'; 5 | import 'format.dart'; 6 | import 'insert.dart'; 7 | 8 | enum RuleType { INSERT, DELETE, FORMAT } 9 | 10 | abstract class Rule { 11 | const Rule(); 12 | 13 | Delta? apply( 14 | Delta document, 15 | int index, { 16 | int? len, 17 | Object? data, 18 | Attribute? attribute, 19 | }) { 20 | validateArgs(len, data, attribute); 21 | return applyRule( 22 | document, 23 | index, 24 | len: len, 25 | data: data, 26 | attribute: attribute, 27 | ); 28 | } 29 | 30 | void validateArgs(int? len, Object? data, Attribute? attribute); 31 | 32 | /// Applies heuristic rule to an operation on a [document] and returns 33 | /// resulting [Delta]. 34 | Delta? applyRule( 35 | Delta document, 36 | int index, { 37 | int? len, 38 | Object? data, 39 | Attribute? attribute, 40 | }); 41 | 42 | RuleType get type; 43 | } 44 | 45 | class Rules { 46 | Rules(this._rules); 47 | 48 | List _customRules = []; 49 | 50 | final List _rules; 51 | static final Rules _instance = Rules([ 52 | const FormatLinkAtCaretPositionRule(), 53 | const ResolveLineFormatRule(), 54 | const ResolveInlineFormatRule(), 55 | const ResolveImageFormatRule(), 56 | const InsertEmbedsRule(), 57 | const AutoExitBlockRule(), 58 | const PreserveBlockStyleOnInsertRule(), 59 | const PreserveLineStyleOnSplitRule(), 60 | const ResetLineFormatOnNewLineRule(), 61 | const AutoFormatLinksRule(), 62 | const AutoFormatMultipleLinksRule(), 63 | const PreserveInlineStylesRule(), 64 | const CatchAllInsertRule(), 65 | const EnsureEmbedLineRule(), 66 | const PreserveLineStyleOnMergeRule(), 67 | const CatchAllDeleteRule(), 68 | const EnsureLastLineBreakDeleteRule() 69 | ]); 70 | 71 | static Rules getInstance() => _instance; 72 | 73 | void setCustomRules(List customRules) { 74 | _customRules = customRules; 75 | } 76 | 77 | Delta apply( 78 | RuleType ruleType, 79 | Document document, 80 | int index, { 81 | int? len, 82 | Object? data, 83 | Attribute? attribute, 84 | }) { 85 | final delta = document.toDelta(); 86 | for (final rule in _customRules + _rules) { 87 | if (rule.type != ruleType) { 88 | continue; 89 | } 90 | try { 91 | final result = rule.apply(delta, index, 92 | len: len, data: data, attribute: attribute); 93 | if (result != null) { 94 | return result..trim(); 95 | } 96 | } catch (e) { 97 | rethrow; 98 | } 99 | } 100 | throw FormatException( 101 | 'Apply delta rules failed. No matching rule found for type: $ruleType', 102 | ); 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /lib/src/utils/delta.dart: -------------------------------------------------------------------------------- 1 | import 'dart:math' as math; 2 | import 'dart:ui'; 3 | 4 | import '../models/documents/attribute.dart'; 5 | import '../models/documents/nodes/node.dart'; 6 | import '../models/quill_delta.dart'; 7 | 8 | // Diff between two texts - old text and new text 9 | class Diff { 10 | Diff(this.start, this.deleted, this.inserted); 11 | 12 | // Start index in old text at which changes begin. 13 | final int start; 14 | 15 | /// The deleted text 16 | final String deleted; 17 | 18 | // The inserted text 19 | final String inserted; 20 | 21 | @override 22 | String toString() { 23 | return 'Diff[$start, "$deleted", "$inserted"]'; 24 | } 25 | } 26 | 27 | /* Get diff operation between old text and new text */ 28 | Diff getDiff(String oldText, String newText, int cursorPosition) { 29 | var end = oldText.length; 30 | final delta = newText.length - end; 31 | for (final limit = math.max(0, cursorPosition - delta); 32 | end > limit && oldText[end - 1] == newText[end + delta - 1]; 33 | end--) {} 34 | var start = 0; 35 | for (final startLimit = cursorPosition - math.max(0, delta); 36 | start < startLimit && oldText[start] == newText[start]; 37 | start++) {} 38 | final deleted = (start >= end) ? '' : oldText.substring(start, end); 39 | final inserted = newText.substring(start, end + delta); 40 | return Diff(start, deleted, inserted); 41 | } 42 | 43 | int getPositionDelta(Delta user, Delta actual) { 44 | if (actual.isEmpty) { 45 | return 0; 46 | } 47 | 48 | final userItr = DeltaIterator(user); 49 | final actualItr = DeltaIterator(actual); 50 | var diff = 0; 51 | while (userItr.hasNext || actualItr.hasNext) { 52 | final length = math.min(userItr.peekLength(), actualItr.peekLength()); 53 | final userOperation = userItr.next(length); 54 | final actualOperation = actualItr.next(length); 55 | if (userOperation.length != actualOperation.length) { 56 | throw 'userOp ${userOperation.length} does not match actualOp ' 57 | '${actualOperation.length}'; 58 | } 59 | if (userOperation.key == actualOperation.key) { 60 | continue; 61 | } else if (userOperation.isInsert && actualOperation.isRetain) { 62 | diff -= userOperation.length!; 63 | } else if (userOperation.isDelete && actualOperation.isRetain) { 64 | diff += userOperation.length!; 65 | } else if (userOperation.isRetain && actualOperation.isInsert) { 66 | String? operationTxt = ''; 67 | if (actualOperation.data is String) { 68 | operationTxt = actualOperation.data as String?; 69 | } 70 | if (operationTxt!.startsWith('\n')) { 71 | continue; 72 | } 73 | diff += actualOperation.length!; 74 | } 75 | } 76 | return diff; 77 | } 78 | 79 | TextDirection getDirectionOfNode(Node node) { 80 | final direction = node.style.attributes[Attribute.direction.key]; 81 | if (direction == Attribute.rtl) { 82 | return TextDirection.rtl; 83 | } 84 | return TextDirection.ltr; 85 | } 86 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/utils.dart: -------------------------------------------------------------------------------- 1 | import 'dart:io' show File; 2 | 3 | import 'package:flutter/foundation.dart' show Uint8List, immutable; 4 | import 'package:gal/gal.dart'; 5 | import 'package:http/http.dart' as http; 6 | 7 | // I would like to orgnize the project structure and the code more 8 | // but here I don't want to change too much since that is a community project 9 | 10 | RegExp _base64 = RegExp( 11 | r'^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=|[A-Za-z0-9+\/]{4})$', 12 | ); 13 | 14 | bool isBase64(String str) { 15 | return _base64.hasMatch(str); 16 | } 17 | 18 | bool isHttpBasedUrl(String url) { 19 | try { 20 | final uri = Uri.parse(url.trim()); 21 | return uri.isScheme('HTTP') || uri.isScheme('HTTPS'); 22 | } catch (_) { 23 | return false; 24 | } 25 | } 26 | 27 | bool isYouTubeUrl(String videoUrl) { 28 | try { 29 | final uri = Uri.parse(videoUrl); 30 | return uri.host == 'www.youtube.com' || 31 | uri.host == 'youtube.com' || 32 | uri.host == 'youtu.be'; 33 | } catch (_) { 34 | return false; 35 | } 36 | } 37 | 38 | bool isImageBase64(String imageUrl) { 39 | return !isHttpBasedUrl(imageUrl) && isBase64(imageUrl); 40 | } 41 | 42 | enum SaveImageResultMethod { network, localStorage } 43 | 44 | @immutable 45 | class _SaveImageResult { 46 | const _SaveImageResult({required this.isSuccess, required this.method}); 47 | 48 | final bool isSuccess; 49 | final SaveImageResultMethod method; 50 | } 51 | 52 | Future<_SaveImageResult> saveImage(String imageUrl) async { 53 | final imageFile = File(imageUrl); 54 | final hasPermission = await Gal.hasAccess(); 55 | final imageExistsLocally = await imageFile.exists(); 56 | if (!hasPermission) { 57 | await Gal.requestAccess(); 58 | } 59 | if (!imageExistsLocally) { 60 | final success = await _saveNetworkImageToLocal(imageUrl); 61 | return _SaveImageResult( 62 | isSuccess: success, 63 | method: SaveImageResultMethod.network, 64 | ); 65 | } 66 | final success = await _saveImageLocally(imageFile); 67 | return _SaveImageResult( 68 | isSuccess: success, 69 | method: SaveImageResultMethod.localStorage, 70 | ); 71 | } 72 | 73 | Future _saveNetworkImageToLocal(String imageUrl) async { 74 | try { 75 | final response = await http.get( 76 | Uri.parse(imageUrl), 77 | ); 78 | if (response.statusCode != 200) { 79 | return false; 80 | } 81 | final imageBytes = response.bodyBytes; 82 | await Gal.putImageBytes(imageBytes); 83 | return true; 84 | } catch (e) { 85 | return false; 86 | } 87 | } 88 | 89 | Future _convertFileToUint8List(File file) async { 90 | try { 91 | final uint8list = await file.readAsBytes(); 92 | return uint8list; 93 | } catch (e) { 94 | return Uint8List(0); 95 | } 96 | } 97 | 98 | Future _saveImageLocally(File imageFile) async { 99 | try { 100 | final imageBytes = await _convertFileToUint8List(imageFile); 101 | await Gal.putImageBytes(imageBytes); 102 | return true; 103 | } catch (e) { 104 | return false; 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "20x20", 5 | "idiom" : "iphone", 6 | "filename" : "Icon-App-20x20@2x.png", 7 | "scale" : "2x" 8 | }, 9 | { 10 | "size" : "20x20", 11 | "idiom" : "iphone", 12 | "filename" : "Icon-App-20x20@3x.png", 13 | "scale" : "3x" 14 | }, 15 | { 16 | "size" : "29x29", 17 | "idiom" : "iphone", 18 | "filename" : "Icon-App-29x29@1x.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "29x29", 23 | "idiom" : "iphone", 24 | "filename" : "Icon-App-29x29@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "29x29", 29 | "idiom" : "iphone", 30 | "filename" : "Icon-App-29x29@3x.png", 31 | "scale" : "3x" 32 | }, 33 | { 34 | "size" : "40x40", 35 | "idiom" : "iphone", 36 | "filename" : "Icon-App-40x40@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "40x40", 41 | "idiom" : "iphone", 42 | "filename" : "Icon-App-40x40@3x.png", 43 | "scale" : "3x" 44 | }, 45 | { 46 | "size" : "60x60", 47 | "idiom" : "iphone", 48 | "filename" : "Icon-App-60x60@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "60x60", 53 | "idiom" : "iphone", 54 | "filename" : "Icon-App-60x60@3x.png", 55 | "scale" : "3x" 56 | }, 57 | { 58 | "size" : "20x20", 59 | "idiom" : "ipad", 60 | "filename" : "Icon-App-20x20@1x.png", 61 | "scale" : "1x" 62 | }, 63 | { 64 | "size" : "20x20", 65 | "idiom" : "ipad", 66 | "filename" : "Icon-App-20x20@2x.png", 67 | "scale" : "2x" 68 | }, 69 | { 70 | "size" : "29x29", 71 | "idiom" : "ipad", 72 | "filename" : "Icon-App-29x29@1x.png", 73 | "scale" : "1x" 74 | }, 75 | { 76 | "size" : "29x29", 77 | "idiom" : "ipad", 78 | "filename" : "Icon-App-29x29@2x.png", 79 | "scale" : "2x" 80 | }, 81 | { 82 | "size" : "40x40", 83 | "idiom" : "ipad", 84 | "filename" : "Icon-App-40x40@1x.png", 85 | "scale" : "1x" 86 | }, 87 | { 88 | "size" : "40x40", 89 | "idiom" : "ipad", 90 | "filename" : "Icon-App-40x40@2x.png", 91 | "scale" : "2x" 92 | }, 93 | { 94 | "size" : "76x76", 95 | "idiom" : "ipad", 96 | "filename" : "Icon-App-76x76@1x.png", 97 | "scale" : "1x" 98 | }, 99 | { 100 | "size" : "76x76", 101 | "idiom" : "ipad", 102 | "filename" : "Icon-App-76x76@2x.png", 103 | "scale" : "2x" 104 | }, 105 | { 106 | "size" : "83.5x83.5", 107 | "idiom" : "ipad", 108 | "filename" : "Icon-App-83.5x83.5@2x.png", 109 | "scale" : "2x" 110 | }, 111 | { 112 | "size" : "1024x1024", 113 | "idiom" : "ios-marketing", 114 | "filename" : "Icon-App-1024x1024@1x.png", 115 | "scale" : "1x" 116 | } 117 | ], 118 | "info" : { 119 | "version" : 1, 120 | "author" : "xcode" 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /example/linux/flutter/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | set(EPHEMERAL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/ephemeral") 4 | 5 | # Configuration provided via flutter tool. 6 | include(${EPHEMERAL_DIR}/generated_config.cmake) 7 | 8 | # TODO: Move the rest of this into files in ephemeral. See 9 | # https://github.com/flutter/flutter/issues/57146. 10 | 11 | # Serves the same purpose as list(TRANSFORM ... PREPEND ...), 12 | # which isn't available in 3.10. 13 | function(list_prepend LIST_NAME PREFIX) 14 | set(NEW_LIST "") 15 | foreach(element ${${LIST_NAME}}) 16 | list(APPEND NEW_LIST "${PREFIX}${element}") 17 | endforeach(element) 18 | set(${LIST_NAME} "${NEW_LIST}" PARENT_SCOPE) 19 | endfunction() 20 | 21 | # === Flutter Library === 22 | # System-level dependencies. 23 | find_package(PkgConfig REQUIRED) 24 | pkg_check_modules(GTK REQUIRED IMPORTED_TARGET gtk+-3.0) 25 | pkg_check_modules(GLIB REQUIRED IMPORTED_TARGET glib-2.0) 26 | pkg_check_modules(GIO REQUIRED IMPORTED_TARGET gio-2.0) 27 | pkg_check_modules(BLKID REQUIRED IMPORTED_TARGET blkid) 28 | pkg_check_modules(LZMA REQUIRED IMPORTED_TARGET liblzma) 29 | 30 | set(FLUTTER_LIBRARY "${EPHEMERAL_DIR}/libflutter_linux_gtk.so") 31 | 32 | # Published to parent scope for install step. 33 | set(FLUTTER_LIBRARY ${FLUTTER_LIBRARY} PARENT_SCOPE) 34 | set(FLUTTER_ICU_DATA_FILE "${EPHEMERAL_DIR}/icudtl.dat" PARENT_SCOPE) 35 | set(PROJECT_BUILD_DIR "${PROJECT_DIR}/build/" PARENT_SCOPE) 36 | set(AOT_LIBRARY "${PROJECT_DIR}/build/lib/libapp.so" PARENT_SCOPE) 37 | 38 | list(APPEND FLUTTER_LIBRARY_HEADERS 39 | "fl_basic_message_channel.h" 40 | "fl_binary_codec.h" 41 | "fl_binary_messenger.h" 42 | "fl_dart_project.h" 43 | "fl_engine.h" 44 | "fl_json_message_codec.h" 45 | "fl_json_method_codec.h" 46 | "fl_message_codec.h" 47 | "fl_method_call.h" 48 | "fl_method_channel.h" 49 | "fl_method_codec.h" 50 | "fl_method_response.h" 51 | "fl_plugin_registrar.h" 52 | "fl_plugin_registry.h" 53 | "fl_standard_message_codec.h" 54 | "fl_standard_method_codec.h" 55 | "fl_string_codec.h" 56 | "fl_value.h" 57 | "fl_view.h" 58 | "flutter_linux.h" 59 | ) 60 | list_prepend(FLUTTER_LIBRARY_HEADERS "${EPHEMERAL_DIR}/flutter_linux/") 61 | add_library(flutter INTERFACE) 62 | target_include_directories(flutter INTERFACE 63 | "${EPHEMERAL_DIR}" 64 | ) 65 | target_link_libraries(flutter INTERFACE "${FLUTTER_LIBRARY}") 66 | target_link_libraries(flutter INTERFACE 67 | PkgConfig::GTK 68 | PkgConfig::GLIB 69 | PkgConfig::GIO 70 | PkgConfig::BLKID 71 | PkgConfig::LZMA 72 | ) 73 | add_dependencies(flutter flutter_assemble) 74 | 75 | # === Flutter tool backend === 76 | # _phony_ is a non-existent file to force this command to run every time, 77 | # since currently there's no way to get a full input/output list from the 78 | # flutter tool. 79 | add_custom_command( 80 | OUTPUT ${FLUTTER_LIBRARY} ${FLUTTER_LIBRARY_HEADERS} 81 | ${CMAKE_CURRENT_BINARY_DIR}/_phony_ 82 | COMMAND ${CMAKE_COMMAND} -E env 83 | ${FLUTTER_TOOL_ENVIRONMENT} 84 | "${FLUTTER_ROOT}/packages/flutter_tools/bin/tool_backend.sh" 85 | ${FLUTTER_TARGET_PLATFORM} ${CMAKE_BUILD_TYPE} 86 | VERBATIM 87 | ) 88 | add_custom_target(flutter_assemble DEPENDS 89 | "${FLUTTER_LIBRARY}" 90 | ${FLUTTER_LIBRARY_HEADERS} 91 | ) 92 | -------------------------------------------------------------------------------- /lib/src/widgets/style_widgets/checkbox_point.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_animate/flutter_animate.dart'; 3 | 4 | import '../../utils/extensions/build_context.dart'; 5 | 6 | class CheckboxPoint extends StatefulWidget { 7 | const CheckboxPoint({ 8 | required this.size, 9 | required this.value, 10 | required this.enabled, 11 | required this.onChanged, 12 | this.uiBuilder, 13 | super.key, 14 | }); 15 | 16 | final double size; 17 | final bool value; 18 | final bool enabled; 19 | final ValueChanged onChanged; 20 | final QuillCheckboxBuilder? uiBuilder; 21 | 22 | @override 23 | _CheckboxPointState createState() => _CheckboxPointState(); 24 | } 25 | 26 | class _CheckboxPointState extends State { 27 | @override 28 | Widget build(BuildContext context) { 29 | final uiBuilder = widget.uiBuilder; 30 | if (uiBuilder != null) { 31 | return uiBuilder.build( 32 | context: context, 33 | isChecked: widget.value, 34 | onChanged: widget.onChanged, 35 | ); 36 | } 37 | final theme = Theme.of(context); 38 | final fillColor = widget.value 39 | ? (widget.enabled 40 | ? theme.colorScheme.primary 41 | : theme.colorScheme.onSurface.withOpacity(0.5)) 42 | : theme.colorScheme.surface; 43 | final borderColor = widget.value 44 | ? (widget.enabled 45 | ? theme.colorScheme.primary 46 | : theme.colorScheme.onSurface.withOpacity(0)) 47 | : (widget.enabled 48 | ? theme.colorScheme.onSurface.withOpacity(0.5) 49 | : theme.colorScheme.onSurface.withOpacity(0.3)); 50 | final child = Container( 51 | alignment: AlignmentDirectional.centerEnd, 52 | padding: EdgeInsetsDirectional.only(end: widget.size / 2), 53 | child: SizedBox( 54 | width: widget.size, 55 | height: widget.size, 56 | child: Material( 57 | color: fillColor, 58 | shape: RoundedRectangleBorder( 59 | side: BorderSide( 60 | color: borderColor, 61 | ), 62 | borderRadius: BorderRadius.circular(2), 63 | ), 64 | child: InkWell( 65 | onTap: 66 | widget.enabled ? () => widget.onChanged(!widget.value) : null, 67 | child: widget.value 68 | ? Icon( 69 | Icons.check, 70 | size: widget.size, 71 | color: theme.colorScheme.onPrimary, 72 | ) 73 | : null, 74 | ), 75 | ), 76 | ), 77 | ); 78 | if (context.requireQuillSharedConfigurations.animationConfigurations 79 | .checkBoxPointItem) { 80 | return Animate( 81 | effects: [ 82 | const SlideEffect( 83 | duration: Duration(milliseconds: 70), 84 | ), 85 | const ScaleEffect( 86 | duration: Duration(milliseconds: 70), 87 | ) 88 | ], 89 | child: child, 90 | ); 91 | } 92 | return child; 93 | } 94 | } 95 | 96 | abstract class QuillCheckboxBuilder { 97 | Widget build({ 98 | required BuildContext context, 99 | required bool isChecked, 100 | required ValueChanged onChanged, 101 | }); 102 | } 103 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/toolbar/video_button.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:flutter_quill/flutter_quill.dart'; 3 | import 'package:image_picker/image_picker.dart'; 4 | 5 | import '../embed_types.dart'; 6 | import 'image_video_utils.dart'; 7 | 8 | class VideoButton extends StatelessWidget { 9 | const VideoButton({ 10 | required this.icon, 11 | required this.controller, 12 | this.iconSize = kDefaultIconSize, 13 | this.onVideoPickCallback, 14 | this.fillColor, 15 | this.filePickImpl, 16 | this.webVideoPickImpl, 17 | this.mediaPickSettingSelector, 18 | this.iconTheme, 19 | this.dialogTheme, 20 | this.tooltip, 21 | this.linkRegExp, 22 | Key? key, 23 | }) : super(key: key); 24 | 25 | final IconData icon; 26 | final double iconSize; 27 | 28 | final Color? fillColor; 29 | 30 | final QuillController controller; 31 | 32 | final OnVideoPickCallback? onVideoPickCallback; 33 | 34 | final WebVideoPickImpl? webVideoPickImpl; 35 | 36 | final FilePickImpl? filePickImpl; 37 | 38 | final MediaPickSettingSelector? mediaPickSettingSelector; 39 | 40 | final QuillIconTheme? iconTheme; 41 | 42 | final QuillDialogTheme? dialogTheme; 43 | 44 | final String? tooltip; 45 | 46 | final RegExp? linkRegExp; 47 | 48 | @override 49 | Widget build(BuildContext context) { 50 | final theme = Theme.of(context); 51 | 52 | final iconColor = iconTheme?.iconUnselectedColor ?? theme.iconTheme.color; 53 | final iconFillColor = 54 | iconTheme?.iconUnselectedFillColor ?? (fillColor ?? theme.canvasColor); 55 | 56 | return QuillToolbarIconButton( 57 | icon: Icon(icon, size: iconSize, color: iconColor), 58 | tooltip: tooltip, 59 | highlightElevation: 0, 60 | hoverElevation: 0, 61 | size: iconSize * 1.77, 62 | fillColor: iconFillColor, 63 | borderRadius: iconTheme?.borderRadius ?? 2, 64 | onPressed: () => _onPressedHandler(context), 65 | ); 66 | } 67 | 68 | Future _onPressedHandler(BuildContext context) async { 69 | if (onVideoPickCallback != null) { 70 | final selector = 71 | mediaPickSettingSelector ?? ImageVideoUtils.selectMediaPickSetting; 72 | final source = await selector(context); 73 | if (source != null) { 74 | if (source == MediaPickSetting.Gallery) { 75 | _pickVideo(context); 76 | } else { 77 | await _typeLink(context); 78 | } 79 | } 80 | } else { 81 | await _typeLink(context); 82 | } 83 | } 84 | 85 | void _pickVideo(BuildContext context) => ImageVideoUtils.handleVideoButtonTap( 86 | context, 87 | controller, 88 | ImageSource.gallery, 89 | onVideoPickCallback!, 90 | filePickImpl: filePickImpl, 91 | webVideoPickImpl: webVideoPickImpl, 92 | ); 93 | 94 | Future _typeLink(BuildContext context) async { 95 | final value = await showDialog( 96 | context: context, 97 | builder: (_) => LinkDialog(dialogTheme: dialogTheme), 98 | ); 99 | _linkSubmitted(value); 100 | } 101 | 102 | void _linkSubmitted(String? value) { 103 | if (value != null && value.isNotEmpty) { 104 | final index = controller.selection.baseOffset; 105 | final length = controller.selection.extentOffset - index; 106 | 107 | controller.replaceText(index, length, BlockEmbed.video(value), null); 108 | } 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 37 | 38 | 39 | 40 | 41 | 42 | 52 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 71 | 73 | 79 | 80 | 81 | 82 | 84 | 85 | 88 | 89 | 90 | -------------------------------------------------------------------------------- /example/lib/universal_ui/universal_ui.dart: -------------------------------------------------------------------------------- 1 | library universal_ui; 2 | 3 | import 'package:flutter/cupertino.dart'; 4 | import 'package:flutter/foundation.dart'; 5 | import 'package:flutter_quill/flutter_quill.dart'; 6 | import 'package:flutter_quill_extensions/flutter_quill_extensions.dart'; 7 | import 'package:universal_html/html.dart' as html; 8 | import 'package:youtube_player_flutter/youtube_player_flutter.dart'; 9 | 10 | import '../widgets/responsive_widget.dart'; 11 | import 'fake_ui.dart' if (dart.library.html) 'real_ui.dart' as ui_instance; 12 | 13 | class PlatformViewRegistryFix { 14 | void registerViewFactory(dynamic x, dynamic y) { 15 | if (kIsWeb) { 16 | ui_instance.PlatformViewRegistry.registerViewFactory( 17 | x, 18 | y, 19 | ); 20 | } 21 | } 22 | } 23 | 24 | class UniversalUI { 25 | PlatformViewRegistryFix platformViewRegistry = PlatformViewRegistryFix(); 26 | } 27 | 28 | var ui = UniversalUI(); 29 | 30 | class ImageEmbedBuilderWeb extends EmbedBuilder { 31 | @override 32 | String get key => BlockEmbed.imageType; 33 | 34 | @override 35 | Widget build( 36 | BuildContext context, 37 | QuillController controller, 38 | Embed node, 39 | bool readOnly, 40 | bool inline, 41 | TextStyle textStyle, 42 | ) { 43 | final imageUrl = node.value.data; 44 | if (isImageBase64(imageUrl)) { 45 | // TODO: handle imageUrl of base64 46 | return const SizedBox(); 47 | } 48 | final size = MediaQuery.sizeOf(context); 49 | UniversalUI().platformViewRegistry.registerViewFactory(imageUrl, (viewId) { 50 | return html.ImageElement() 51 | ..src = imageUrl 52 | ..style.height = 'auto' 53 | ..style.width = 'auto'; 54 | }); 55 | return Padding( 56 | padding: EdgeInsets.only( 57 | right: ResponsiveWidget.isMediumScreen(context) 58 | ? size.width * 0.5 59 | : (ResponsiveWidget.isLargeScreen(context)) 60 | ? size.width * 0.75 61 | : size.width * 0.2, 62 | ), 63 | child: SizedBox( 64 | height: MediaQuery.sizeOf(context).height * 0.45, 65 | child: HtmlElementView( 66 | viewType: imageUrl, 67 | ), 68 | ), 69 | ); 70 | } 71 | } 72 | 73 | class VideoEmbedBuilderWeb extends EmbedBuilder { 74 | @override 75 | String get key => BlockEmbed.videoType; 76 | 77 | @override 78 | Widget build( 79 | BuildContext context, 80 | QuillController controller, 81 | Embed node, 82 | bool readOnly, 83 | bool inline, 84 | TextStyle textStyle, 85 | ) { 86 | var videoUrl = node.value.data; 87 | if (videoUrl.contains('youtube.com') || videoUrl.contains('youtu.be')) { 88 | final youtubeID = YoutubePlayer.convertUrlToId(videoUrl); 89 | if (youtubeID != null) { 90 | videoUrl = 'https://www.youtube.com/embed/$youtubeID'; 91 | } 92 | } 93 | 94 | UniversalUI().platformViewRegistry.registerViewFactory( 95 | videoUrl, 96 | (id) => html.IFrameElement() 97 | ..width = MediaQuery.sizeOf(context).width.toString() 98 | ..height = MediaQuery.sizeOf(context).height.toString() 99 | ..src = videoUrl 100 | ..style.border = 'none'); 101 | 102 | return SizedBox( 103 | height: 500, 104 | child: HtmlElementView( 105 | viewType: videoUrl, 106 | ), 107 | ); 108 | } 109 | } 110 | 111 | List get defaultEmbedBuildersWeb => [ 112 | ImageEmbedBuilderWeb(), 113 | VideoEmbedBuilderWeb(), 114 | ]; 115 | -------------------------------------------------------------------------------- /flutter_quill_extensions/lib/embeds/widgets/image_resizer.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/cupertino.dart'; 2 | import 'package:flutter/foundation.dart'; 3 | import 'package:flutter/material.dart'; 4 | import 'package:flutter/scheduler.dart'; 5 | import 'package:flutter_quill/translations.dart'; 6 | 7 | class ImageResizer extends StatefulWidget { 8 | const ImageResizer( 9 | {required this.imageWidth, 10 | required this.imageHeight, 11 | required this.maxWidth, 12 | required this.maxHeight, 13 | required this.onImageResize, 14 | Key? key}) 15 | : super(key: key); 16 | 17 | final double? imageWidth; 18 | final double? imageHeight; 19 | final double maxWidth; 20 | final double maxHeight; 21 | final Function(double, double) onImageResize; 22 | 23 | @override 24 | _ImageResizerState createState() => _ImageResizerState(); 25 | } 26 | 27 | class _ImageResizerState extends State { 28 | late double _width; 29 | late double _height; 30 | 31 | @override 32 | void initState() { 33 | super.initState(); 34 | _width = widget.imageWidth ?? widget.maxWidth; 35 | _height = widget.imageHeight ?? widget.maxHeight; 36 | } 37 | 38 | @override 39 | Widget build(BuildContext context) { 40 | switch (defaultTargetPlatform) { 41 | case TargetPlatform.iOS: 42 | return _showCupertinoMenu(); 43 | case TargetPlatform.android: 44 | return _showMaterialMenu(); 45 | case TargetPlatform.macOS: 46 | case TargetPlatform.windows: 47 | case TargetPlatform.linux: 48 | case TargetPlatform.fuchsia: 49 | return _showMaterialMenu(); 50 | default: 51 | throw 'Not supposed to be invoked for $defaultTargetPlatform'; 52 | } 53 | } 54 | 55 | Widget _showMaterialMenu() { 56 | return Column( 57 | mainAxisSize: MainAxisSize.min, 58 | children: [_widthSlider(), _heightSlider()], 59 | ); 60 | } 61 | 62 | Widget _showCupertinoMenu() { 63 | return CupertinoActionSheet(actions: [ 64 | CupertinoActionSheetAction( 65 | onPressed: () {}, 66 | child: _widthSlider(), 67 | ), 68 | CupertinoActionSheetAction( 69 | onPressed: () {}, 70 | child: _heightSlider(), 71 | ) 72 | ]); 73 | } 74 | 75 | Widget _slider( 76 | double value, 77 | double max, 78 | String label, 79 | ValueChanged onChanged, 80 | ) { 81 | return Padding( 82 | padding: const EdgeInsets.symmetric(horizontal: 8), 83 | child: Card( 84 | child: Slider( 85 | value: value, 86 | max: max, 87 | divisions: 1000, 88 | label: label.i18n, 89 | onChanged: (val) { 90 | setState(() { 91 | onChanged(val); 92 | _resizeImage(); 93 | }); 94 | }, 95 | ), 96 | )); 97 | } 98 | 99 | Widget _heightSlider() { 100 | return _slider(_height, widget.maxHeight, 'Height', (value) { 101 | _height = value; 102 | }); 103 | } 104 | 105 | Widget _widthSlider() { 106 | return _slider(_width, widget.maxWidth, 'Width', (value) { 107 | _width = value; 108 | }); 109 | } 110 | 111 | bool _scheduled = false; 112 | 113 | void _resizeImage() { 114 | if (_scheduled) { 115 | return; 116 | } 117 | 118 | _scheduled = true; 119 | SchedulerBinding.instance.addPostFrameCallback((_) { 120 | widget.onImageResize(_width, _height); 121 | _scheduled = false; 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 44 | 54 | 56 | 62 | 63 | 64 | 65 | 66 | 67 | 73 | 75 | 81 | 82 | 83 | 84 | 86 | 87 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /lib/src/widgets/style_widgets/number_point.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | 3 | import '../../models/documents/attribute.dart'; 4 | import '../text_block.dart'; 5 | 6 | class QuillNumberPoint extends StatelessWidget { 7 | const QuillNumberPoint({ 8 | required this.index, 9 | required this.indentLevelCounts, 10 | required this.count, 11 | required this.style, 12 | required this.width, 13 | required this.attrs, 14 | this.withDot = true, 15 | this.padding = 0.0, 16 | Key? key, 17 | }) : super(key: key); 18 | 19 | final int index; 20 | final Map indentLevelCounts; 21 | final int count; 22 | final TextStyle style; 23 | final double width; 24 | final Map attrs; 25 | final bool withDot; 26 | final double padding; 27 | 28 | @override 29 | Widget build(BuildContext context) { 30 | var s = index.toString(); 31 | int? level = 0; 32 | if (!attrs.containsKey(Attribute.indent.key) && indentLevelCounts.isEmpty) { 33 | indentLevelCounts.clear(); 34 | indentLevelCounts[0] = 1; 35 | return Container( 36 | alignment: AlignmentDirectional.topEnd, 37 | width: width, 38 | padding: EdgeInsetsDirectional.only(end: padding), 39 | child: Text(withDot ? '$s.' : s, style: style), 40 | ); 41 | } 42 | if (attrs.containsKey(Attribute.indent.key)) { 43 | level = attrs[Attribute.indent.key]!.value; 44 | } else if (!indentLevelCounts.containsKey(0)) { 45 | // first level but is back from previous indent level 46 | // supposed to be "2." 47 | indentLevelCounts[0] = 1; 48 | } 49 | if (indentLevelCounts.containsKey(level! + 1)) { 50 | // last visited level is done, going up 51 | indentLevelCounts.remove(level + 1); 52 | } 53 | final count = (indentLevelCounts[level] ?? 0) + 1; 54 | indentLevelCounts[level] = count; 55 | 56 | s = count.toString(); 57 | if (level % 3 == 1) { 58 | // a. b. c. d. e. ... 59 | s = _toExcelSheetColumnTitle(count); 60 | } else if (level % 3 == 2) { 61 | // i. ii. iii. ... 62 | s = _intToRoman(count); 63 | } 64 | // level % 3 == 0 goes back to 1. 2. 3. 65 | 66 | return Container( 67 | alignment: AlignmentDirectional.topEnd, 68 | width: width, 69 | padding: EdgeInsetsDirectional.only(end: padding), 70 | child: Text(withDot ? '$s.' : s, style: style), 71 | ); 72 | } 73 | 74 | String _toExcelSheetColumnTitle(int n) { 75 | final result = StringBuffer(); 76 | while (n > 0) { 77 | n--; 78 | result.write(String.fromCharCode((n % 26).floor() + 97)); 79 | n = (n / 26).floor(); 80 | } 81 | 82 | return result.toString().split('').reversed.join(); 83 | } 84 | 85 | String _intToRoman(int input) { 86 | var num = input; 87 | 88 | if (num < 0) { 89 | return ''; 90 | } else if (num == 0) { 91 | return 'nulla'; 92 | } 93 | 94 | final builder = StringBuffer(); 95 | for (var a = 0; a < arabianRomanNumbers.length; a++) { 96 | final times = (num / arabianRomanNumbers[a]) 97 | .truncate(); // equals 1 only when arabianRomanNumbers[a] = num 98 | // executes n times where n is the number of times you have to add 99 | // the current roman number value to reach current num. 100 | builder.write(romanNumbers[a] * times); 101 | num -= times * 102 | arabianRomanNumbers[ 103 | a]; // subtract previous roman number value from num 104 | } 105 | 106 | return builder.toString().toLowerCase(); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /example/windows/runner/Runner.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #pragma code_page(65001) 4 | #include "resource.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | 22 | #ifdef APSTUDIO_INVOKED 23 | ///////////////////////////////////////////////////////////////////////////// 24 | // 25 | // TEXTINCLUDE 26 | // 27 | 28 | 1 TEXTINCLUDE 29 | BEGIN 30 | "resource.h\0" 31 | END 32 | 33 | 2 TEXTINCLUDE 34 | BEGIN 35 | "#include ""winres.h""\r\n" 36 | "\0" 37 | END 38 | 39 | 3 TEXTINCLUDE 40 | BEGIN 41 | "\r\n" 42 | "\0" 43 | END 44 | 45 | #endif // APSTUDIO_INVOKED 46 | 47 | 48 | ///////////////////////////////////////////////////////////////////////////// 49 | // 50 | // Icon 51 | // 52 | 53 | // Icon with lowest ID value placed first to ensure application icon 54 | // remains consistent on all systems. 55 | IDI_APP_ICON ICON "resources\\app_icon.ico" 56 | 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | #if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD) 64 | #define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD 65 | #else 66 | #define VERSION_AS_NUMBER 1,0,0,0 67 | #endif 68 | 69 | #if defined(FLUTTER_VERSION) 70 | #define VERSION_AS_STRING FLUTTER_VERSION 71 | #else 72 | #define VERSION_AS_STRING "1.0.0" 73 | #endif 74 | 75 | VS_VERSION_INFO VERSIONINFO 76 | FILEVERSION VERSION_AS_NUMBER 77 | PRODUCTVERSION VERSION_AS_NUMBER 78 | FILEFLAGSMASK VS_FFI_FILEFLAGSMASK 79 | #ifdef _DEBUG 80 | FILEFLAGS VS_FF_DEBUG 81 | #else 82 | FILEFLAGS 0x0L 83 | #endif 84 | FILEOS VOS__WINDOWS32 85 | FILETYPE VFT_APP 86 | FILESUBTYPE 0x0L 87 | BEGIN 88 | BLOCK "StringFileInfo" 89 | BEGIN 90 | BLOCK "040904e4" 91 | BEGIN 92 | VALUE "CompanyName", "com.example" "\0" 93 | VALUE "FileDescription", "A new Flutter project." "\0" 94 | VALUE "FileVersion", VERSION_AS_STRING "\0" 95 | VALUE "InternalName", "app" "\0" 96 | VALUE "LegalCopyright", "Copyright (C) 2021 com.example. All rights reserved." "\0" 97 | VALUE "OriginalFilename", "app.exe" "\0" 98 | VALUE "ProductName", "app" "\0" 99 | VALUE "ProductVersion", VERSION_AS_STRING "\0" 100 | END 101 | END 102 | BLOCK "VarFileInfo" 103 | BEGIN 104 | VALUE "Translation", 0x409, 1252 105 | END 106 | END 107 | 108 | #endif // English (United States) resources 109 | ///////////////////////////////////////////////////////////////////////////// 110 | 111 | 112 | 113 | #ifndef APSTUDIO_INVOKED 114 | ///////////////////////////////////////////////////////////////////////////// 115 | // 116 | // Generated from the TEXTINCLUDE 3 resource. 117 | // 118 | 119 | 120 | ///////////////////////////////////////////////////////////////////////////// 121 | #endif // not APSTUDIO_INVOKED 122 | -------------------------------------------------------------------------------- /example/windows/runner/win32_window.h: -------------------------------------------------------------------------------- 1 | #ifndef RUNNER_WIN32_WINDOW_H_ 2 | #define RUNNER_WIN32_WINDOW_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // A class abstraction for a high DPI-aware Win32 Window. Intended to be 11 | // inherited from by classes that wish to specialize with custom 12 | // rendering and input handling 13 | class Win32Window { 14 | public: 15 | struct Point { 16 | unsigned int x; 17 | unsigned int y; 18 | Point(unsigned int x, unsigned int y) : x(x), y(y) {} 19 | }; 20 | 21 | struct Size { 22 | unsigned int width; 23 | unsigned int height; 24 | Size(unsigned int width, unsigned int height) 25 | : width(width), height(height) {} 26 | }; 27 | 28 | Win32Window(); 29 | virtual ~Win32Window(); 30 | 31 | // Creates and shows a win32 window with |title| and position and size using 32 | // |origin| and |size|. New windows are created on the default monitor. Window 33 | // sizes are specified to the OS in physical pixels, hence to ensure a 34 | // consistent size to will treat the width height passed in to this function 35 | // as logical pixels and scale to appropriate for the default monitor. Returns 36 | // true if the window was created successfully. 37 | bool CreateAndShow(const std::wstring& title, 38 | const Point& origin, 39 | const Size& size); 40 | 41 | // Release OS resources associated with window. 42 | void Destroy(); 43 | 44 | // Inserts |content| into the window tree. 45 | void SetChildContent(HWND content); 46 | 47 | // Returns the backing Window handle to enable clients to set icon and other 48 | // window properties. Returns nullptr if the window has been destroyed. 49 | HWND GetHandle(); 50 | 51 | // If true, closing this window will quit the application. 52 | void SetQuitOnClose(bool quit_on_close); 53 | 54 | // Return a RECT representing the bounds of the current client area. 55 | RECT GetClientArea(); 56 | 57 | protected: 58 | // Processes and route salient window messages for mouse handling, 59 | // size change and DPI. Delegates handling of these to member overloads that 60 | // inheriting classes can handle. 61 | virtual LRESULT MessageHandler(HWND window, 62 | UINT const message, 63 | WPARAM const wparam, 64 | LPARAM const lparam) noexcept; 65 | 66 | // Called when CreateAndShow is called, allowing subclass window-related 67 | // setup. Subclasses should return false if setup fails. 68 | virtual bool OnCreate(); 69 | 70 | // Called when Destroy is called. 71 | virtual void OnDestroy(); 72 | 73 | private: 74 | friend class WindowClassRegistrar; 75 | 76 | // OS callback called by message pump. Handles the WM_NCCREATE message which 77 | // is passed when the non-client area is being created and enables automatic 78 | // non-client DPI scaling so that the non-client area automatically 79 | // responsponds to changes in DPI. All other messages are handled by 80 | // MessageHandler. 81 | static LRESULT CALLBACK WndProc(HWND const window, 82 | UINT const message, 83 | WPARAM const wparam, 84 | LPARAM const lparam) noexcept; 85 | 86 | // Retrieves a class instance pointer for |window| 87 | static Win32Window* GetThisFromHandle(HWND const window) noexcept; 88 | 89 | bool quit_on_close_ = false; 90 | 91 | // window handle for top level window. 92 | HWND window_handle_ = nullptr; 93 | 94 | // window handle for hosted content. 95 | HWND child_content_ = nullptr; 96 | }; 97 | 98 | #endif // RUNNER_WIN32_WINDOW_H_ 99 | -------------------------------------------------------------------------------- /example/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(app LANGUAGES CXX) 3 | 4 | set(BINARY_NAME "app") 5 | 6 | cmake_policy(SET CMP0063 NEW) 7 | 8 | set(CMAKE_INSTALL_RPATH "$ORIGIN/lib") 9 | 10 | # Configure build options. 11 | get_property(IS_MULTICONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) 12 | if(IS_MULTICONFIG) 13 | set(CMAKE_CONFIGURATION_TYPES "Debug;Profile;Release" 14 | CACHE STRING "" FORCE) 15 | else() 16 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 17 | set(CMAKE_BUILD_TYPE "Debug" CACHE 18 | STRING "Flutter build mode" FORCE) 19 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS 20 | "Debug" "Profile" "Release") 21 | endif() 22 | endif() 23 | 24 | set(CMAKE_EXE_LINKER_FLAGS_PROFILE "${CMAKE_EXE_LINKER_FLAGS_RELEASE}") 25 | set(CMAKE_SHARED_LINKER_FLAGS_PROFILE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE}") 26 | set(CMAKE_C_FLAGS_PROFILE "${CMAKE_C_FLAGS_RELEASE}") 27 | set(CMAKE_CXX_FLAGS_PROFILE "${CMAKE_CXX_FLAGS_RELEASE}") 28 | 29 | # Use Unicode for all projects. 30 | add_definitions(-DUNICODE -D_UNICODE) 31 | 32 | # Compilation settings that should be applied to most targets. 33 | function(APPLY_STANDARD_SETTINGS TARGET) 34 | target_compile_features(${TARGET} PUBLIC cxx_std_17) 35 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd"4100") 36 | target_compile_options(${TARGET} PRIVATE /EHsc) 37 | target_compile_definitions(${TARGET} PRIVATE "_HAS_EXCEPTIONS=0") 38 | target_compile_definitions(${TARGET} PRIVATE "$<$:_DEBUG>") 39 | endfunction() 40 | 41 | set(FLUTTER_MANAGED_DIR "${CMAKE_CURRENT_SOURCE_DIR}/flutter") 42 | 43 | # Flutter library and tool build rules. 44 | add_subdirectory(${FLUTTER_MANAGED_DIR}) 45 | 46 | # Application build 47 | add_subdirectory("runner") 48 | 49 | # Generated plugin build rules, which manage building the plugins and adding 50 | # them to the application. 51 | include(flutter/generated_plugins.cmake) 52 | 53 | 54 | # === Installation === 55 | # Support files are copied into place next to the executable, so that it can 56 | # run in place. This is done instead of making a separate bundle (as on Linux) 57 | # so that building and running from within Visual Studio will work. 58 | set(BUILD_BUNDLE_DIR "$") 59 | # Make the "install" step default, as it's required to run. 60 | set(CMAKE_VS_INCLUDE_INSTALL_TO_DEFAULT_BUILD 1) 61 | if(CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 62 | set(CMAKE_INSTALL_PREFIX "${BUILD_BUNDLE_DIR}" CACHE PATH "..." FORCE) 63 | endif() 64 | 65 | set(INSTALL_BUNDLE_DATA_DIR "${CMAKE_INSTALL_PREFIX}/data") 66 | set(INSTALL_BUNDLE_LIB_DIR "${CMAKE_INSTALL_PREFIX}") 67 | 68 | install(TARGETS ${BINARY_NAME} RUNTIME DESTINATION "${CMAKE_INSTALL_PREFIX}" 69 | COMPONENT Runtime) 70 | 71 | install(FILES "${FLUTTER_ICU_DATA_FILE}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 72 | COMPONENT Runtime) 73 | 74 | install(FILES "${FLUTTER_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 75 | COMPONENT Runtime) 76 | 77 | if(PLUGIN_BUNDLED_LIBRARIES) 78 | install(FILES "${PLUGIN_BUNDLED_LIBRARIES}" 79 | DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" 80 | COMPONENT Runtime) 81 | endif() 82 | 83 | # Fully re-copy the assets directory on each build to avoid having stale files 84 | # from a previous install. 85 | set(FLUTTER_ASSET_DIR_NAME "flutter_assets") 86 | install(CODE " 87 | file(REMOVE_RECURSE \"${INSTALL_BUNDLE_DATA_DIR}/${FLUTTER_ASSET_DIR_NAME}\") 88 | " COMPONENT Runtime) 89 | install(DIRECTORY "${PROJECT_BUILD_DIR}/${FLUTTER_ASSET_DIR_NAME}" 90 | DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" COMPONENT Runtime) 91 | 92 | # Install the AOT library on non-Debug builds only. 93 | install(FILES "${AOT_LIBRARY}" DESTINATION "${INSTALL_BUNDLE_DATA_DIR}" 94 | CONFIGURATIONS Profile;Release 95 | COMPONENT Runtime) 96 | --------------------------------------------------------------------------------