From fcb09e452b5fa70e0aa4233a6b84bcd68aa07c43 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 16 May 2024 14:13:14 +0300 Subject: [PATCH 01/10] ci: upload the LICENSE to the release assets for new releases. --- .github/workflows/publish.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1e9d929e..5afd5600 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,6 +13,12 @@ jobs: steps: - uses: actions/checkout@v4 + - name: Upload the License to the release assets + uses: actions/upload-artifact@v4 + with: + name: LICENSE + path: LICENSE + - uses: subosito/flutter-action@v2 with: channel: 'stable' @@ -26,9 +32,6 @@ jobs: - name: Install dependencies run: flutter pub get - - # Here you can insert custom steps you need - # - run: dart tool/generate-code.dart # This is needed in order for the authentication to success # dart pub token add https://pub.dev --env-var PUB_TOKEN From e3e322063433fde2aad80bf9277a3d88a3a55063 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 16 May 2024 14:14:09 +0300 Subject: [PATCH 02/10] chore: update packages and the example to work with the latest flutter stable SDK --- example/macos/Podfile | 4 ++++ example/macos/Podfile.lock | 4 ++-- example/pubspec.yaml | 7 ++++++- pubspec.yaml | 2 +- 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/example/macos/Podfile b/example/macos/Podfile index dbccf89c..35ac8883 100644 --- a/example/macos/Podfile +++ b/example/macos/Podfile @@ -39,5 +39,9 @@ end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_macos_build_settings(target) + # TODO: Workaround to fix build failure + target.build_configurations.each do |config| + config.build_settings['MACOSX_DEPLOYMENT_TARGET'] = '11.0' + end end end diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 08466b43..92ae5979 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -98,8 +98,8 @@ SPEC CHECKSUMS: sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec super_native_extensions: 85efee3a7495b46b04befcfc86ed12069264ebf3 url_launcher_macos: d2691c7dd33ed713bf3544850a623080ec693d95 - video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 + video_player_avfoundation: 2b4384f3b157206b5e150a0083cdc0c905d260d3 -PODFILE CHECKSUM: c2e95c8c0fe03c5c57e438583cae4cc732296009 +PODFILE CHECKSUM: 7159dd71cf9f57a5669bb2dee7a5030dbcc0483f COCOAPODS: 1.15.2 diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 27581e18..360a03ab 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,7 +19,7 @@ dependencies: flutter_quill_test: ^9.3.4 quill_html_converter: ^9.3.4 quill_pdf_converter: ^9.3.4 - # No^9.2.10ackages + # Normal packages path: ^1.8.3 equatable: ^2.0.5 cross_file: ^0.3.4 @@ -63,6 +63,11 @@ dependency_overrides: quill_pdf_converter: path: ../quill_pdf_converter + # TODO: Temporarily add this so the example can work + flutter_colorpicker: + git: https://github.com/mchome/flutter_colorpicker.git + pdf_widget_wrapper: ^1.0.4 + dev_dependencies: flutter_test: diff --git a/pubspec.yaml b/pubspec.yaml index ad1f3779..47e59e7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -43,7 +43,7 @@ dependencies: sdk: flutter # Normal packages - intl: ^0.18.1 + intl: ^0.19.0 dart_quill_delta: ^9.3.3 collection: ^1.17.0 quiver: ^3.2.1 From 04e0775cc83d92d986fb47b59e752ddb57ae5e53 Mon Sep 17 00:00:00 2001 From: Ellet Date: Thu, 16 May 2024 14:23:58 +0300 Subject: [PATCH 03/10] chore: update old deprecated code --- lib/src/widgets/toolbar/buttons/link_style2_button.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/src/widgets/toolbar/buttons/link_style2_button.dart b/lib/src/widgets/toolbar/buttons/link_style2_button.dart index 3b2f5a12..ff54f117 100644 --- a/lib/src/widgets/toolbar/buttons/link_style2_button.dart +++ b/lib/src/widgets/toolbar/buttons/link_style2_button.dart @@ -281,7 +281,7 @@ class _LinkStyleDialogState extends State { ? Theme.of(context) .elevatedButtonTheme .style - ?.copyWith(fixedSize: MaterialStatePropertyAll(widget.buttonSize)) + ?.copyWith(fixedSize: WidgetStatePropertyAll(widget.buttonSize)) : widget.dialogTheme?.buttonStyle; final isWrappable = widget.dialogTheme?.isWrappable ?? false; From cc48a2539eb7fa13e46625612bebba49336eebbb Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Sat, 18 May 2024 13:21:12 +0800 Subject: [PATCH 04/10] feat: Add `readOnlyMouseCursor` to config mouse cursor type (#1873) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🌍Add `readOnlyMouseCursor` to config mouse cursor type Related issue: [Bug] Mouse cursor is still text insertion type when `readOnly` is `false` #1752 * 🔧Fix import --- .../config/raw_editor/raw_editor_configurations.dart | 7 ++++++- .../widgets/raw_editor/raw_editor_render_object.dart | 5 +++-- lib/src/widgets/raw_editor/raw_editor_state.dart | 12 ++++++++---- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/lib/src/models/config/raw_editor/raw_editor_configurations.dart b/lib/src/models/config/raw_editor/raw_editor_configurations.dart index eb1f9def..51906cfe 100644 --- a/lib/src/models/config/raw_editor/raw_editor_configurations.dart +++ b/lib/src/models/config/raw_editor/raw_editor_configurations.dart @@ -22,7 +22,9 @@ import 'package:flutter/widgets.dart' TextFieldTapRegion, TextSelectionControls, ValueChanged, - Widget; + Widget, + MouseCursor, + SystemMouseCursors; import 'package:meta/meta.dart' show immutable; import '../../../widgets/others/cursor.dart'; @@ -174,6 +176,9 @@ class QuillRawEditorConfigurations extends Equatable { /// The style to be used for the editing cursor. final CursorStyle cursorStyle; + /// The [readOnlyMouseCursor] is used for Windows, macOS when [readOnly] is [true] + final MouseCursor readOnlyMouseCursor = SystemMouseCursors.text; + /// Configures how the platform keyboard will select an uppercase or /// lowercase keyboard. /// diff --git a/lib/src/widgets/raw_editor/raw_editor_render_object.dart b/lib/src/widgets/raw_editor/raw_editor_render_object.dart index af99671f..ec9fb2cd 100644 --- a/lib/src/widgets/raw_editor/raw_editor_render_object.dart +++ b/lib/src/widgets/raw_editor/raw_editor_render_object.dart @@ -5,8 +5,9 @@ import '../../models/documents/document.dart'; import '../editor/editor.dart'; import '../others/cursor.dart'; -class QuilRawEditorMultiChildRenderObject extends MultiChildRenderObjectWidget { - const QuilRawEditorMultiChildRenderObject({ +class QuillRawEditorMultiChildRenderObject + extends MultiChildRenderObjectWidget { + const QuillRawEditorMultiChildRenderObject({ required super.children, required this.document, required this.textDirection, diff --git a/lib/src/widgets/raw_editor/raw_editor_state.dart b/lib/src/widgets/raw_editor/raw_editor_state.dart index e452f4fd..07e5f013 100644 --- a/lib/src/widgets/raw_editor/raw_editor_state.dart +++ b/lib/src/widgets/raw_editor/raw_editor_state.dart @@ -483,8 +483,10 @@ class QuillRawEditorState extends EditorState viewportBuilder: (_, offset) => CompositedTransformTarget( link: _toolbarLayerLink, child: MouseRegion( - cursor: SystemMouseCursors.text, - child: QuilRawEditorMultiChildRenderObject( + cursor: widget.configurations.readOnly + ? widget.configurations.readOnlyMouseCursor + : SystemMouseCursors.text, + child: QuillRawEditorMultiChildRenderObject( key: _editorKey, offset: offset, document: doc, @@ -515,8 +517,10 @@ class QuillRawEditorState extends EditorState link: _toolbarLayerLink, child: Semantics( child: MouseRegion( - cursor: SystemMouseCursors.text, - child: QuilRawEditorMultiChildRenderObject( + cursor: widget.configurations.readOnly + ? widget.configurations.readOnlyMouseCursor + : SystemMouseCursors.text, + child: QuillRawEditorMultiChildRenderObject( key: _editorKey, document: doc, selection: controller.selection, From 9599f4b82bbd52b7dfb0e2e88fe0875078f13d91 Mon Sep 17 00:00:00 2001 From: Cheryl Date: Fri, 17 May 2024 22:23:46 -0700 Subject: [PATCH 05/10] Upgrade to 9.3.12 --- CHANGELOG.md | 3 +++ dart_quill_delta/CHANGELOG.md | 3 +++ dart_quill_delta/pubspec.yaml | 2 +- flutter_quill_extensions/CHANGELOG.md | 3 +++ flutter_quill_extensions/pubspec.yaml | 2 +- flutter_quill_test/CHANGELOG.md | 3 +++ flutter_quill_test/pubspec.yaml | 2 +- pubspec.yaml | 2 +- quill_html_converter/CHANGELOG.md | 3 +++ quill_html_converter/pubspec.yaml | 2 +- quill_pdf_converter/CHANGELOG.md | 3 +++ quill_pdf_converter/pubspec.yaml | 2 +- version.dart | 2 +- 13 files changed, 25 insertions(+), 7 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/dart_quill_delta/CHANGELOG.md b/dart_quill_delta/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/dart_quill_delta/CHANGELOG.md +++ b/dart_quill_delta/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/dart_quill_delta/pubspec.yaml b/dart_quill_delta/pubspec.yaml index 84b99dea..5dc7388c 100644 --- a/dart_quill_delta/pubspec.yaml +++ b/dart_quill_delta/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_quill_delta description: A port of quill-js-delta from typescript to dart -version: 9.3.11 +version: 9.3.12 homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ repository: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index fe49272b..b5319b35 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_extensions description: Embed extensions for flutter_quill including image, video, formula and etc. -version: 9.3.11 +version: 9.3.12 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/flutter_quill_test/CHANGELOG.md b/flutter_quill_test/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/flutter_quill_test/CHANGELOG.md +++ b/flutter_quill_test/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/flutter_quill_test/pubspec.yaml b/flutter_quill_test/pubspec.yaml index 3e0e3486..68a4c1ed 100644 --- a/flutter_quill_test/pubspec.yaml +++ b/flutter_quill_test/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_test description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases. -version: 9.3.11 +version: 9.3.12 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/pubspec.yaml b/pubspec.yaml index 47e59e7f..4eb9954d 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. -version: 9.3.11 +version: 9.3.12 homepage: https://1o24bbs.com/c/bulletjournal/108/ repository: https://github.com/singerdmx/flutter-quill/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_html_converter/CHANGELOG.md b/quill_html_converter/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/quill_html_converter/CHANGELOG.md +++ b/quill_html_converter/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/quill_html_converter/pubspec.yaml b/quill_html_converter/pubspec.yaml index 7ea7f857..83fd79ea 100644 --- a/quill_html_converter/pubspec.yaml +++ b/quill_html_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_html_converter description: A extension for flutter_quill package to add support for dealing with conversion to/from html -version: 9.3.11 +version: 9.3.12 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_pdf_converter/CHANGELOG.md b/quill_pdf_converter/CHANGELOG.md index a4d26e5b..f7d8ac0d 100644 --- a/quill_pdf_converter/CHANGELOG.md +++ b/quill_pdf_converter/CHANGELOG.md @@ -2,6 +2,9 @@ All notable changes to this project will be documented in this file. +## 9.3.12 +* Add `readOnlyMouseCursor` to config mouse cursor type + ## 9.3.11 * Fix typo in QuillHtmlConverter * Fix re-create checkbox diff --git a/quill_pdf_converter/pubspec.yaml b/quill_pdf_converter/pubspec.yaml index 95f99b61..b0ee64a9 100644 --- a/quill_pdf_converter/pubspec.yaml +++ b/quill_pdf_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_pdf_converter description: A extension for flutter_quill package to add support for dealing with conversion to pdf -version: 9.3.11 +version: 9.3.12 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/version.dart b/version.dart index d655354b..f32c85eb 100644 --- a/version.dart +++ b/version.dart @@ -1 +1 @@ -const version = '9.3.11'; +const version = '9.3.12'; From dd23f7aacea4e201e8e1a65096d1a02d60ae5625 Mon Sep 17 00:00:00 2001 From: Ellet <73608287+ellet0@users.noreply.github.com> Date: Sat, 18 May 2024 13:53:01 +0300 Subject: [PATCH 06/10] Feat/support latest stable flutter (#1874) * fix: temporarily remove flutter_colorpicker from pub.dev * chore: clone flutter_colorpicker from Github, add a TODO with it * fix: update color_dialog.dart to use color picker package from the lib/src/packages/flutter_colorpicker * refactor(example): remove the old android example and recreate it to get it working with the latest stable version without any warrnings * fix: format flutter_colorpicker to fix CI failure, update the android example project to use latest version of Kotlin, fix AndroidManifest string resources * fix: update the linux example to fix CI failure * ci: update build.yml as an attemp to fix building the Linux application * ci: add a todo in build.yml, remove flutter doctor check * ci: fix a typo * ci: update the name of each step --- .github/workflows/build.yml | 19 +- example/.metadata | 25 +- example/android/app/build.gradle | 47 +- .../android/app/src/main/AndroidManifest.xml | 4 +- .../app/src/main/res/values/strings.xml | 4 - example/android/build.gradle | 60 +- example/android/gradle.properties | 5 +- .../gradle/wrapper/gradle-wrapper.properties | 2 +- example/android/settings.gradle | 20 +- example/devtools_options.yaml | 1 - example/linux/CMakeLists.txt | 6 + example/linux/my_application.cc | 20 + example/pubspec.yaml | 3 - .../flutter_colorpicker.dart | 9 + .../flutter_colorpicker/src/block_picker.dart | 211 +++ .../flutter_colorpicker/src/colorpicker.dart | 891 ++++++++++ .../flutter_colorpicker/src/colors.dart | 172 ++ .../src/material_picker.dart | 384 +++++ .../flutter_colorpicker/src/palette.dart | 1523 +++++++++++++++++ .../flutter_colorpicker/src/utils.dart | 224 +++ .../toolbar/buttons/color/color_dialog.dart | 4 +- pubspec.yaml | 3 +- 22 files changed, 3499 insertions(+), 138 deletions(-) delete mode 100644 example/android/app/src/main/res/values/strings.xml delete mode 100644 example/devtools_options.yaml create mode 100644 lib/src/packages/flutter_colorpicker/flutter_colorpicker.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/block_picker.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/colorpicker.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/colors.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/material_picker.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/palette.dart create mode 100644 lib/src/packages/flutter_colorpicker/src/utils.dart diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 72a76630..a0344f60 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,3 +1,5 @@ +# TODO: Update the workflow to build on Android, iOS, desktop (macOS and Windows) instead of just Linux and Web + name: Build the example on: @@ -16,26 +18,29 @@ jobs: channel: 'stable' cache: true - - name: Check flutter version + - name: Check Flutter Version run: flutter --version - - name: Enable Local Dev + - name: Enable Local Development Environment (use the local packages) run: ./scripts/enable_local_dev.sh - - name: Install dependencies + - name: Install Flutter Dependencies run: flutter pub get - - name: Flutter build Web + - name: Build Flutter Web Application run: flutter build web --release --verbose --dart-define=CI=true working-directory: ./example - - name: Updates APT Linux Package Lists && Upgrade + - name: Update and Upgrade APT Packages run: sudo apt update -y && sudo apt upgrade -y - - name: Install flutter Linux prerequisites + - name: Install Flutter Linux Prerequisites run: sudo apt install -y curl git unzip xz-utils zip libglu1-mesa - - name: Flutter build Linux + - name: Install Flutter Linux Desktop Dependencies + run: sudo apt install -y clang cmake git ninja-build pkg-config libgtk-3-dev liblzma-dev libstdc++-12-dev + + - name: Build Flutter Linux Desktop Application run: flutter build linux --release --verbose --dart-define=CI=true working-directory: ./example diff --git a/example/.metadata b/example/.metadata index d22992ed..e724e168 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,7 @@ # This file should be version controlled and should not be manually edited. version: - revision: "db7ef5bf9f59442b0e200a90587e8fa5e0c6336a" + revision: "5dcb86f68f239346676ceb1ed1ea385bd215fba1" channel: "stable" project_type: app @@ -13,26 +13,11 @@ project_type: app migration: platforms: - platform: root - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: android - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: ios - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 - platform: linux - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: macos - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: web - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - - platform: windows - create_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a - base_revision: db7ef5bf9f59442b0e200a90587e8fa5e0c6336a + create_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 + base_revision: 5dcb86f68f239346676ceb1ed1ea385bd215fba1 # User provided section diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index aa36e402..b8a6b7f8 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,62 +1,53 @@ plugins { id "com.android.application" id "kotlin-android" + // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id "dev.flutter.flutter-gradle-plugin" } def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') +def localPropertiesFile = rootProject.file("local.properties") if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> + localPropertiesFile.withReader("UTF-8") { reader -> localProperties.load(reader) } } -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') +def flutterVersionCode = localProperties.getProperty("flutter.versionCode") if (flutterVersionCode == null) { - flutterVersionCode = '1' + flutterVersionCode = "1" } -def flutterVersionName = localProperties.getProperty('flutter.versionName') +def flutterVersionName = localProperties.getProperty("flutter.versionName") if (flutterVersionName == null) { - flutterVersionName = '1.0' + flutterVersionName = "1.0" } android { - namespace "com.example.example" - compileSdkVersion flutter.compileSdkVersion - ndkVersion flutter.ndkVersion + namespace = "com.example.example" + compileSdk = flutter.compileSdkVersion + ndkVersion = flutter.ndkVersion compileOptions { - sourceCompatibility JavaVersion.VERSION_17 - targetCompatibility JavaVersion.VERSION_17 - } - - kotlinOptions { - jvmTarget = '1.8' - } - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 } defaultConfig { - applicationId "com.example.example" - minSdkVersion 23 - targetSdkVersion flutter.targetSdkVersion - versionCode flutterVersionCode.toInteger() - versionName flutterVersionName + applicationId = "com.example.example" + minSdk = 23 + targetSdk = flutter.targetSdkVersion + versionCode = flutterVersionCode.toInteger() + versionName = flutterVersionName } buildTypes { release { - signingConfig signingConfigs.debug + signingConfig = signingConfigs.debug } } } flutter { - source '../..' + source = "../.." } - -dependencies {} diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index bf856053..e68e61df 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -23,7 +23,7 @@ + android:label="Flutter Quill Example"> - + \ No newline at end of file diff --git a/example/android/app/src/main/res/values/strings.xml b/example/android/app/src/main/res/values/strings.xml deleted file mode 100644 index 5c509355..00000000 --- a/example/android/app/src/main/res/values/strings.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - Flutter Quill Demo - \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle index 76a4bae8..d2ffbffa 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,18 +1,3 @@ -import org.jetbrains.kotlin.gradle.tasks.KotlinCompile - -buildscript { - ext.kotlin_version = '1.9.21' - repositories { - google() - mavenCentral() - } - - dependencies { - classpath 'com.android.tools.build:gradle:8.2.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { google() @@ -20,53 +5,12 @@ allprojects { } } -rootProject.buildDir = '../build' +rootProject.buildDir = "../build" subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" - - // For mode details visit https://gist.github.com/ellet0/93fefb39e48c40592bda3931e05fd35c - afterEvaluate { - // check if android block is available - - if (it.hasProperty('android')) { - - if (it.android.namespace == null) { - def manifest = new XmlSlurper().parse(file(it.android.sourceSets.main.manifest.srcFile)) - def packageName = manifest.@package.text() - println("Setting ${packageName} as android namespace in build.gradle from the AndroidManifest.xml") - android.namespace = packageName - } - - def javaVersion = JavaVersion.VERSION_17 - println("Changes will be applied for the following packages:") - android { - def androidApiVersion = 34 -// compileSdkVersion androidApiVersion - compileSdk androidApiVersion - defaultConfig { - targetSdkVersion androidApiVersion - } - compileOptions { - sourceCompatibility javaVersion - targetCompatibility javaVersion - } - tasks.withType(KotlinCompile).configureEach { - buildscript { - ext.kotlin_version = kotlin_version - } - kotlinOptions { - jvmTarget = javaVersion.toString() - } - } - String message = "For package ${android.namespace} by update compileSdkVersion, targetSdkVersion \n to $androidApiVersion and java version to ${javaVersion.toString()}" - println(message) - } - } - - } } subprojects { - project.evaluationDependsOn(':app') + project.evaluationDependsOn(":app") } tasks.register("clean", Delete) { diff --git a/example/android/gradle.properties b/example/android/gradle.properties index b9a9a246..3b5b324f 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,6 +1,3 @@ -org.gradle.jvmargs=-Xmx1536M +org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true -android.defaults.buildfeatures.buildconfig=true -android.nonTransitiveRClass=false -android.nonFinalResIds=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index aa49780c..e1ca574e 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 55c4ca8b..ee3c79c1 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -5,16 +5,22 @@ pluginManagement { def flutterSdkPath = properties.getProperty("flutter.sdk") assert flutterSdkPath != null, "flutter.sdk not set in local.properties" return flutterSdkPath - } - settings.ext.flutterSdkPath = flutterSdkPath() + }() - includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle") + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - plugins { - id "dev.flutter.flutter-gradle-plugin" version "1.0.0" apply false + repositories { + google() + mavenCentral() + gradlePluginPortal() } } -include ":app" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "7.3.0" apply false + // TODO: We should update the project to not require higher version of Kotlin + id "org.jetbrains.kotlin.android" version "1.9.24" apply false +} -apply from: "${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle/app_plugin_loader.gradle" +include ":app" diff --git a/example/devtools_options.yaml b/example/devtools_options.yaml deleted file mode 100644 index 7e7e7f67..00000000 --- a/example/devtools_options.yaml +++ /dev/null @@ -1 +0,0 @@ -extensions: diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt index d67bd4e0..9cb0d1dd 100644 --- a/example/linux/CMakeLists.txt +++ b/example/linux/CMakeLists.txt @@ -123,6 +123,12 @@ foreach(bundled_library ${PLUGIN_BUNDLED_LIBRARIES}) COMPONENT Runtime) endforeach(bundled_library) +# Copy the native assets provided by the build.dart from all packages. +set(NATIVE_ASSETS_DIR "${PROJECT_BUILD_DIR}native_assets/linux/") +install(DIRECTORY "${NATIVE_ASSETS_DIR}" + DESTINATION "${INSTALL_BUNDLE_LIB_DIR}" + COMPONENT Runtime) + # Fully re-copy the assets directory on each build to avoid having stale files # from a previous install. set(FLUTTER_ASSET_DIR_NAME "flutter_assets") diff --git a/example/linux/my_application.cc b/example/linux/my_application.cc index 0ba8f430..c0530d42 100644 --- a/example/linux/my_application.cc +++ b/example/linux/my_application.cc @@ -81,6 +81,24 @@ static gboolean my_application_local_command_line(GApplication* application, gch return TRUE; } +// Implements GApplication::startup. +static void my_application_startup(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application startup. + + G_APPLICATION_CLASS(my_application_parent_class)->startup(application); +} + +// Implements GApplication::shutdown. +static void my_application_shutdown(GApplication* application) { + //MyApplication* self = MY_APPLICATION(object); + + // Perform any actions required at application shutdown. + + G_APPLICATION_CLASS(my_application_parent_class)->shutdown(application); +} + // Implements GObject::dispose. static void my_application_dispose(GObject* object) { MyApplication* self = MY_APPLICATION(object); @@ -91,6 +109,8 @@ static void my_application_dispose(GObject* object) { static void my_application_class_init(MyApplicationClass* klass) { G_APPLICATION_CLASS(klass)->activate = my_application_activate; G_APPLICATION_CLASS(klass)->local_command_line = my_application_local_command_line; + G_APPLICATION_CLASS(klass)->startup = my_application_startup; + G_APPLICATION_CLASS(klass)->shutdown = my_application_shutdown; G_OBJECT_CLASS(klass)->dispose = my_application_dispose; } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 360a03ab..85d52bf0 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -63,9 +63,6 @@ dependency_overrides: quill_pdf_converter: path: ../quill_pdf_converter - # TODO: Temporarily add this so the example can work - flutter_colorpicker: - git: https://github.com/mchome/flutter_colorpicker.git pdf_widget_wrapper: ^1.0.4 diff --git a/lib/src/packages/flutter_colorpicker/flutter_colorpicker.dart b/lib/src/packages/flutter_colorpicker/flutter_colorpicker.dart new file mode 100644 index 00000000..fee43157 --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/flutter_colorpicker.dart @@ -0,0 +1,9 @@ +library flutter_colorpicker; + +// TODO: temporarily clone https://pub.dev/packages/flutter_colorpicker as it's hasn't been published on pub.dev for a while + +export 'src/block_picker.dart'; +export 'src/colorpicker.dart'; +export 'src/material_picker.dart'; +export 'src/palette.dart'; +export 'src/utils.dart'; diff --git a/lib/src/packages/flutter_colorpicker/src/block_picker.dart b/lib/src/packages/flutter_colorpicker/src/block_picker.dart new file mode 100644 index 00000000..fd6c488b --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/block_picker.dart @@ -0,0 +1,211 @@ +// ignore_for_file: type=lint + +/// Blocky Color Picker + +library block_colorpicker; + +import 'package:flutter/material.dart'; +import 'utils.dart'; + +/// Child widget for layout builder. +typedef PickerItem = Widget Function(Color color); + +/// Customize the layout. +typedef PickerLayoutBuilder = Widget Function( + BuildContext context, List colors, PickerItem child); + +/// Customize the item shape. +typedef PickerItemBuilder = Widget Function( + Color color, bool isCurrentColor, void Function() changeColor); + +// Provide a list of colors for block color picker. +const List _defaultColors = [ + Colors.red, + Colors.pink, + Colors.purple, + Colors.deepPurple, + Colors.indigo, + Colors.blue, + Colors.lightBlue, + Colors.cyan, + Colors.teal, + Colors.green, + Colors.lightGreen, + Colors.lime, + Colors.yellow, + Colors.amber, + Colors.orange, + Colors.deepOrange, + Colors.brown, + Colors.grey, + Colors.blueGrey, + Colors.black, +]; + +// Provide a layout for [BlockPicker]. +Widget _defaultLayoutBuilder( + BuildContext context, List colors, PickerItem child) { + Orientation orientation = MediaQuery.of(context).orientation; + + return SizedBox( + width: 300, + height: orientation == Orientation.portrait ? 360 : 200, + child: GridView.count( + crossAxisCount: orientation == Orientation.portrait ? 4 : 6, + crossAxisSpacing: 5, + mainAxisSpacing: 5, + children: [for (Color color in colors) child(color)], + ), + ); +} + +// Provide a shape for [BlockPicker]. +Widget _defaultItemBuilder( + Color color, bool isCurrentColor, void Function() changeColor) { + return Container( + margin: const EdgeInsets.all(7), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: color, + boxShadow: [ + BoxShadow( + color: color.withOpacity(0.8), + offset: const Offset(1, 2), + blurRadius: 5) + ], + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: changeColor, + borderRadius: BorderRadius.circular(50), + child: AnimatedOpacity( + duration: const Duration(milliseconds: 210), + opacity: isCurrentColor ? 1 : 0, + child: Icon(Icons.done, + color: useWhiteForeground(color) ? Colors.white : Colors.black), + ), + ), + ), + ); +} + +// The blocky color picker you can alter the layout and shape. +class BlockPicker extends StatefulWidget { + const BlockPicker({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + this.availableColors = _defaultColors, + this.useInShowDialog = true, + this.layoutBuilder = _defaultLayoutBuilder, + this.itemBuilder = _defaultItemBuilder, + }) : super(key: key); + + final Color? pickerColor; + final ValueChanged onColorChanged; + final List availableColors; + final bool useInShowDialog; + final PickerLayoutBuilder layoutBuilder; + final PickerItemBuilder itemBuilder; + + @override + State createState() => _BlockPickerState(); +} + +class _BlockPickerState extends State { + Color? _currentColor; + + @override + void initState() { + _currentColor = widget.pickerColor; + super.initState(); + } + + void changeColor(Color color) { + setState(() => _currentColor = color); + widget.onColorChanged(color); + } + + @override + Widget build(BuildContext context) { + return widget.layoutBuilder( + context, + widget.availableColors, + (Color color) => widget.itemBuilder( + color, + (_currentColor != null && + (widget.useInShowDialog ? true : widget.pickerColor != null)) + ? (_currentColor?.value == color.value) && + (widget.useInShowDialog + ? true + : widget.pickerColor?.value == color.value) + : false, + () => changeColor(color), + ), + ); + } +} + +// The blocky color picker you can alter the layout and shape with multiple choice. +class MultipleChoiceBlockPicker extends StatefulWidget { + const MultipleChoiceBlockPicker({ + Key? key, + required this.pickerColors, + required this.onColorsChanged, + this.availableColors = _defaultColors, + this.useInShowDialog = true, + this.layoutBuilder = _defaultLayoutBuilder, + this.itemBuilder = _defaultItemBuilder, + }) : super(key: key); + + final List? pickerColors; + final ValueChanged> onColorsChanged; + final List availableColors; + final bool useInShowDialog; + final PickerLayoutBuilder layoutBuilder; + final PickerItemBuilder itemBuilder; + + @override + State createState() => _MultipleChoiceBlockPickerState(); +} + +class _MultipleChoiceBlockPickerState extends State { + List? _currentColors; + + @override + void initState() { + _currentColors = widget.pickerColors; + super.initState(); + } + + void toggleColor(Color color) { + setState(() { + if (_currentColors != null) { + _currentColors!.contains(color) + ? _currentColors!.remove(color) + : _currentColors!.add(color); + } + }); + widget.onColorsChanged(_currentColors ?? []); + } + + @override + Widget build(BuildContext context) { + return widget.layoutBuilder( + context, + widget.availableColors, + (Color color) => widget.itemBuilder( + color, + (_currentColors != null && + (widget.useInShowDialog ? true : widget.pickerColors != null)) + ? _currentColors!.contains(color) && + (widget.useInShowDialog + ? true + : widget.pickerColors!.contains(color)) + : false, + () => toggleColor(color), + ), + ); + } +} diff --git a/lib/src/packages/flutter_colorpicker/src/colorpicker.dart b/lib/src/packages/flutter_colorpicker/src/colorpicker.dart new file mode 100644 index 00000000..68c8f649 --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/colorpicker.dart @@ -0,0 +1,891 @@ +// ignore_for_file: type=lint + +/// HSV(HSB)/HSL Color Picker example +/// +/// You can create your own layout by importing `picker.dart`. + +library hsv_picker; + +import 'package:flutter/material.dart'; +import 'palette.dart'; +import 'utils.dart'; + +/// The default layout of Color Picker. +class ColorPicker extends StatefulWidget { + const ColorPicker({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + this.pickerHsvColor, + this.onHsvColorChanged, + this.paletteType = PaletteType.hsvWithHue, + this.enableAlpha = true, + @Deprecated('Use empty list in [labelTypes] to disable label.') + this.showLabel = true, + this.labelTypes = const [ + ColorLabelType.rgb, + ColorLabelType.hsv, + ColorLabelType.hsl + ], + @Deprecated( + 'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') + this.labelTextStyle, + this.displayThumbColor = false, + this.portraitOnly = false, + this.colorPickerWidth = 300.0, + this.pickerAreaHeightPercent = 1.0, + this.pickerAreaBorderRadius = const BorderRadius.all(Radius.zero), + this.hexInputBar = false, + this.hexInputController, + this.colorHistory, + this.onHistoryChanged, + }) : super(key: key); + + final Color pickerColor; + final ValueChanged onColorChanged; + final HSVColor? pickerHsvColor; + final ValueChanged? onHsvColorChanged; + final PaletteType paletteType; + final bool enableAlpha; + final bool showLabel; + final List labelTypes; + final TextStyle? labelTextStyle; + final bool displayThumbColor; + final bool portraitOnly; + final double colorPickerWidth; + final double pickerAreaHeightPercent; + final BorderRadius pickerAreaBorderRadius; + final bool hexInputBar; + + /// Allows setting the color using text input, via [TextEditingController]. + /// + /// Listens to [String] input and trying to convert it to the valid [Color]. + /// Contains basic validator, that requires final input to be provided + /// in one of those formats: + /// + /// * RGB + /// * #RGB + /// * RRGGBB + /// * #RRGGBB + /// * AARRGGBB + /// * #AARRGGBB + /// + /// Where: A stands for Alpha, R for Red, G for Green, and B for blue color. + /// It will only accept 3/6/8 long HEXs with an optional hash (`#`) at the beginning. + /// Allowed characters are Latin A-F case insensitive and numbers 0-9. + /// It does respect the [enableAlpha] flag, so if alpha is disabled, all inputs + /// with transparency are also converted to non-transparent color values. + /// ```dart + /// MaterialButton( + /// elevation: 3.0, + /// onPressed: () { + /// // The initial value can be provided directly to the controller. + /// final textController = + /// TextEditingController(text: '#2F19DB'); + /// showDialog( + /// context: context, + /// builder: (BuildContext context) { + /// return AlertDialog( + /// scrollable: true, + /// titlePadding: const EdgeInsets.all(0.0), + /// contentPadding: const EdgeInsets.all(0.0), + /// content: Column( + /// children: [ + /// ColorPicker( + /// pickerColor: currentColor, + /// onColorChanged: changeColor, + /// colorPickerWidth: 300.0, + /// pickerAreaHeightPercent: 0.7, + /// enableAlpha: + /// true, // hexInputController will respect it too. + /// displayThumbColor: true, + /// showLabel: true, + /// paletteType: PaletteType.hsv, + /// pickerAreaBorderRadius: const BorderRadius.only( + /// topLeft: const Radius.circular(2.0), + /// topRight: const Radius.circular(2.0), + /// ), + /// hexInputController: textController, // <- here + /// portraitOnly: true, + /// ), + /// Padding( + /// padding: const EdgeInsets.all(16), + /// /* It can be any text field, for example: + /// * TextField + /// * TextFormField + /// * CupertinoTextField + /// * EditableText + /// * any text field from 3-rd party package + /// * your own text field + /// so basically anything that supports/uses + /// a TextEditingController for an editable text. + /// */ + /// child: CupertinoTextField( + /// controller: textController, + /// // Everything below is purely optional. + /// prefix: Padding( + /// padding: const EdgeInsets.only(left: 8), + /// child: const Icon(Icons.tag), + /// ), + /// suffix: IconButton( + /// icon: + /// const Icon(Icons.content_paste_rounded), + /// onPressed: () async => + /// copyToClipboard(textController.text), + /// ), + /// autofocus: true, + /// maxLength: 9, + /// inputFormatters: [ + /// // Any custom input formatter can be passed + /// // here or use any Form validator you want. + /// UpperCaseTextFormatter(), + /// FilteringTextInputFormatter.allow( + /// RegExp(kValidHexPattern)), + /// ], + /// ), + /// ) + /// ], + /// ), + /// ); + /// }, + /// ); + /// }, + /// child: const Text('Change me via text input'), + /// color: currentColor, + /// textColor: useWhiteForeground(currentColor) + /// ? const Color(0xffffffff) + /// : const Color(0xff000000), + /// ), + /// ``` + /// + /// Do not forget to `dispose()` your [TextEditingController] if you creating + /// it inside any kind of [StatefulWidget]'s [State]. + /// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet + final TextEditingController? hexInputController; + final List? colorHistory; + final ValueChanged>? onHistoryChanged; + + @override + _ColorPickerState createState() => _ColorPickerState(); +} + +class _ColorPickerState extends State { + HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); + List colorHistory = []; + + @override + void initState() { + currentHsvColor = (widget.pickerHsvColor != null) + ? widget.pickerHsvColor as HSVColor + : HSVColor.fromColor(widget.pickerColor); + // If there's no initial text in `hexInputController`, + if (widget.hexInputController?.text.isEmpty == true) { + // set it to the current's color HEX value. + widget.hexInputController?.text = colorToHex( + currentHsvColor.toColor(), + enableAlpha: widget.enableAlpha, + ); + } + // Listen to the text input, If there is an `hexInputController` provided. + widget.hexInputController?.addListener(colorPickerTextInputListener); + if (widget.colorHistory != null && widget.onHistoryChanged != null) { + colorHistory = widget.colorHistory ?? []; + } + super.initState(); + } + + @override + void didUpdateWidget(ColorPicker oldWidget) { + super.didUpdateWidget(oldWidget); + currentHsvColor = (widget.pickerHsvColor != null) + ? widget.pickerHsvColor as HSVColor + : HSVColor.fromColor(widget.pickerColor); + } + + void colorPickerTextInputListener() { + // It can't be null really, since it's only listening if the controller + // is provided, but it may help to calm the Dart analyzer in the future. + if (widget.hexInputController == null) return; + // If a user is inserting/typing any text — try to get the color value from it, + // and interpret its transparency, dependent on the widget's settings. + final Color? color = colorFromHex(widget.hexInputController!.text, + enableAlpha: widget.enableAlpha); + // If it's the valid color: + if (color != null) { + // set it as the current color and + setState(() => currentHsvColor = HSVColor.fromColor(color)); + // notify with a callback. + widget.onColorChanged(color); + if (widget.onHsvColorChanged != null) + widget.onHsvColorChanged!(currentHsvColor); + } + } + + @override + void dispose() { + widget.hexInputController?.removeListener(colorPickerTextInputListener); + super.dispose(); + } + + Widget colorPickerSlider(TrackType trackType) { + return ColorPickerSlider( + trackType, + currentHsvColor, + (HSVColor color) { + // Update text in `hexInputController` if provided. + widget.hexInputController?.text = + colorToHex(color.toColor(), enableAlpha: widget.enableAlpha); + setState(() => currentHsvColor = color); + widget.onColorChanged(currentHsvColor.toColor()); + if (widget.onHsvColorChanged != null) + widget.onHsvColorChanged!(currentHsvColor); + }, + displayThumbColor: widget.displayThumbColor, + ); + } + + void onColorChanging(HSVColor color) { + // Update text in `hexInputController` if provided. + widget.hexInputController?.text = + colorToHex(color.toColor(), enableAlpha: widget.enableAlpha); + setState(() => currentHsvColor = color); + widget.onColorChanged(currentHsvColor.toColor()); + if (widget.onHsvColorChanged != null) + widget.onHsvColorChanged!(currentHsvColor); + } + + Widget colorPicker() { + return ClipRRect( + borderRadius: widget.pickerAreaBorderRadius, + child: Padding( + padding: + EdgeInsets.all(widget.paletteType == PaletteType.hueWheel ? 10 : 0), + child: ColorPickerArea( + currentHsvColor, onColorChanging, widget.paletteType), + ), + ); + } + + Widget sliderByPaletteType() { + switch (widget.paletteType) { + case PaletteType.hsv: + case PaletteType.hsvWithHue: + case PaletteType.hsl: + case PaletteType.hslWithHue: + return colorPickerSlider(TrackType.hue); + case PaletteType.hsvWithValue: + case PaletteType.hueWheel: + return colorPickerSlider(TrackType.value); + case PaletteType.hsvWithSaturation: + return colorPickerSlider(TrackType.saturation); + case PaletteType.hslWithLightness: + return colorPickerSlider(TrackType.lightness); + case PaletteType.hslWithSaturation: + return colorPickerSlider(TrackType.saturationForHSL); + case PaletteType.rgbWithBlue: + return colorPickerSlider(TrackType.blue); + case PaletteType.rgbWithGreen: + return colorPickerSlider(TrackType.green); + case PaletteType.rgbWithRed: + return colorPickerSlider(TrackType.red); + default: + return const SizedBox(); + } + } + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).orientation == Orientation.portrait || + widget.portraitOnly) { + return Column( + children: [ + SizedBox( + width: widget.colorPickerWidth, + height: widget.colorPickerWidth * widget.pickerAreaHeightPercent, + child: colorPicker(), + ), + Padding( + padding: const EdgeInsets.fromLTRB(15.0, 5.0, 10.0, 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => setState(() { + if (widget.onHistoryChanged != null && + !colorHistory.contains(currentHsvColor.toColor())) { + colorHistory.add(currentHsvColor.toColor()); + widget.onHistoryChanged!(colorHistory); + } + }), + child: ColorIndicator(currentHsvColor), + ), + Expanded( + child: Column( + children: [ + SizedBox( + height: 40.0, + width: widget.colorPickerWidth - 75.0, + child: sliderByPaletteType()), + if (widget.enableAlpha) + SizedBox( + height: 40.0, + width: widget.colorPickerWidth - 75.0, + child: colorPickerSlider(TrackType.alpha), + ), + ], + ), + ), + ], + ), + ), + if (colorHistory.isNotEmpty) + SizedBox( + width: widget.colorPickerWidth, + height: 50, + child: + ListView(scrollDirection: Axis.horizontal, children: [ + for (Color color in colorHistory) + Padding( + key: Key(color.hashCode.toString()), + padding: const EdgeInsets.fromLTRB(15, 0, 0, 10), + child: Center( + child: GestureDetector( + onTap: () => onColorChanging(HSVColor.fromColor(color)), + child: ColorIndicator(HSVColor.fromColor(color), + width: 30, height: 30), + ), + ), + ), + const SizedBox(width: 15), + ]), + ), + if (widget.showLabel && widget.labelTypes.isNotEmpty) + FittedBox( + child: ColorPickerLabel( + currentHsvColor, + enableAlpha: widget.enableAlpha, + textStyle: widget.labelTextStyle, + colorLabelTypes: widget.labelTypes, + ), + ), + if (widget.hexInputBar) + ColorPickerInput( + currentHsvColor.toColor(), + (Color color) { + setState(() => currentHsvColor = HSVColor.fromColor(color)); + widget.onColorChanged(currentHsvColor.toColor()); + if (widget.onHsvColorChanged != null) + widget.onHsvColorChanged!(currentHsvColor); + }, + enableAlpha: widget.enableAlpha, + embeddedText: false, + ), + const SizedBox(height: 20.0), + ], + ); + } else { + return Row( + children: [ + SizedBox( + width: widget.colorPickerWidth, + height: widget.colorPickerWidth * widget.pickerAreaHeightPercent, + child: colorPicker()), + Column( + children: [ + Row( + children: [ + const SizedBox(width: 20.0), + GestureDetector( + onTap: () => setState(() { + if (widget.onHistoryChanged != null && + !colorHistory.contains(currentHsvColor.toColor())) { + colorHistory.add(currentHsvColor.toColor()); + widget.onHistoryChanged!(colorHistory); + } + }), + child: ColorIndicator(currentHsvColor), + ), + Column( + children: [ + SizedBox( + height: 40.0, + width: 260.0, + child: sliderByPaletteType()), + if (widget.enableAlpha) + SizedBox( + height: 40.0, + width: 260.0, + child: colorPickerSlider(TrackType.alpha)), + ], + ), + const SizedBox(width: 10.0), + ], + ), + if (colorHistory.isNotEmpty) + SizedBox( + width: widget.colorPickerWidth, + height: 50, + child: ListView( + scrollDirection: Axis.horizontal, + children: [ + for (Color color in colorHistory) + Padding( + key: Key(color.hashCode.toString()), + padding: const EdgeInsets.fromLTRB(15, 18, 0, 0), + child: Center( + child: GestureDetector( + onTap: () => + onColorChanging(HSVColor.fromColor(color)), + onLongPress: () { + if (colorHistory.remove(color)) { + widget.onHistoryChanged!(colorHistory); + setState(() {}); + } + }, + child: ColorIndicator(HSVColor.fromColor(color), + width: 30, height: 30), + ), + ), + ), + const SizedBox(width: 15), + ]), + ), + const SizedBox(height: 20.0), + if (widget.showLabel && widget.labelTypes.isNotEmpty) + FittedBox( + child: ColorPickerLabel( + currentHsvColor, + enableAlpha: widget.enableAlpha, + textStyle: widget.labelTextStyle, + colorLabelTypes: widget.labelTypes, + ), + ), + if (widget.hexInputBar) + ColorPickerInput( + currentHsvColor.toColor(), + (Color color) { + setState(() => currentHsvColor = HSVColor.fromColor(color)); + widget.onColorChanged(currentHsvColor.toColor()); + if (widget.onHsvColorChanged != null) + widget.onHsvColorChanged!(currentHsvColor); + }, + enableAlpha: widget.enableAlpha, + embeddedText: false, + ), + const SizedBox(height: 5), + ], + ), + ], + ); + } + } +} + +/// The Color Picker with sliders only. Support HSV, HSL and RGB color model. +class SlidePicker extends StatefulWidget { + const SlidePicker({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + this.colorModel = ColorModel.rgb, + this.enableAlpha = true, + this.sliderSize = const Size(260, 40), + this.showSliderText = true, + @Deprecated( + 'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') + this.sliderTextStyle, + this.showParams = true, + @Deprecated('Use empty list in [labelTypes] to disable label.') + this.showLabel = true, + this.labelTypes = const [], + @Deprecated( + 'Use Theme.of(context).textTheme.bodyText1 & 2 to alter text style.') + this.labelTextStyle, + this.showIndicator = true, + this.indicatorSize = const Size(280, 50), + this.indicatorAlignmentBegin = const Alignment(-1.0, -3.0), + this.indicatorAlignmentEnd = const Alignment(1.0, 3.0), + this.displayThumbColor = true, + this.indicatorBorderRadius = const BorderRadius.all(Radius.zero), + }) : super(key: key); + + final Color pickerColor; + final ValueChanged onColorChanged; + final ColorModel colorModel; + final bool enableAlpha; + final Size sliderSize; + final bool showSliderText; + final TextStyle? sliderTextStyle; + final bool showLabel; + final bool showParams; + final List labelTypes; + final TextStyle? labelTextStyle; + final bool showIndicator; + final Size indicatorSize; + final AlignmentGeometry indicatorAlignmentBegin; + final AlignmentGeometry indicatorAlignmentEnd; + final bool displayThumbColor; + final BorderRadius indicatorBorderRadius; + + @override + State createState() => _SlidePickerState(); +} + +class _SlidePickerState extends State { + HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); + + @override + void initState() { + super.initState(); + currentHsvColor = HSVColor.fromColor(widget.pickerColor); + } + + @override + void didUpdateWidget(SlidePicker oldWidget) { + super.didUpdateWidget(oldWidget); + currentHsvColor = HSVColor.fromColor(widget.pickerColor); + } + + Widget colorPickerSlider(TrackType trackType) { + return ColorPickerSlider( + trackType, + currentHsvColor, + (HSVColor color) { + setState(() => currentHsvColor = color); + widget.onColorChanged(currentHsvColor.toColor()); + }, + displayThumbColor: widget.displayThumbColor, + fullThumbColor: true, + ); + } + + Widget indicator() { + return ClipRRect( + borderRadius: widget.indicatorBorderRadius, + clipBehavior: Clip.antiAliasWithSaveLayer, + child: GestureDetector( + onTap: () { + setState( + () => currentHsvColor = HSVColor.fromColor(widget.pickerColor)); + widget.onColorChanged(currentHsvColor.toColor()); + }, + child: Container( + width: widget.indicatorSize.width, + height: widget.indicatorSize.height, + margin: const EdgeInsets.only(bottom: 15.0), + foregroundDecoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + widget.pickerColor, + widget.pickerColor, + currentHsvColor.toColor(), + currentHsvColor.toColor(), + ], + begin: widget.indicatorAlignmentBegin, + end: widget.indicatorAlignmentEnd, + stops: const [0.0, 0.5, 0.5, 1.0], + ), + ), + child: const CustomPaint(painter: CheckerPainter()), + ), + ), + ); + } + + String getColorParams(int pos) { + assert(pos >= 0 && pos < 4); + if (widget.colorModel == ColorModel.rgb) { + final Color color = currentHsvColor.toColor(); + return [ + color.red.toString(), + color.green.toString(), + color.blue.toString(), + '${(color.opacity * 100).round()}', + ][pos]; + } else if (widget.colorModel == ColorModel.hsv) { + return [ + currentHsvColor.hue.round().toString(), + (currentHsvColor.saturation * 100).round().toString(), + (currentHsvColor.value * 100).round().toString(), + (currentHsvColor.alpha * 100).round().toString(), + ][pos]; + } else if (widget.colorModel == ColorModel.hsl) { + HSLColor hslColor = hsvToHsl(currentHsvColor); + return [ + hslColor.hue.round().toString(), + (hslColor.saturation * 100).round().toString(), + (hslColor.lightness * 100).round().toString(), + (currentHsvColor.alpha * 100).round().toString(), + ][pos]; + } else { + return '??'; + } + } + + @override + Widget build(BuildContext context) { + double fontSize = 14; + if (widget.labelTextStyle != null && + widget.labelTextStyle?.fontSize != null) { + fontSize = widget.labelTextStyle?.fontSize ?? 14; + } + final List trackTypes = [ + if (widget.colorModel == ColorModel.hsv) ...[ + TrackType.hue, + TrackType.saturation, + TrackType.value + ], + if (widget.colorModel == ColorModel.hsl) ...[ + TrackType.hue, + TrackType.saturationForHSL, + TrackType.lightness + ], + if (widget.colorModel == ColorModel.rgb) ...[ + TrackType.red, + TrackType.green, + TrackType.blue + ], + if (widget.enableAlpha) ...[TrackType.alpha], + ]; + List sliders = [ + for (TrackType trackType in trackTypes) + SizedBox( + width: widget.sliderSize.width, + height: widget.sliderSize.height, + child: Row( + children: [ + if (widget.showSliderText) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10.0), + child: Text( + trackType.toString().split('.').last[0].toUpperCase(), + style: widget.sliderTextStyle ?? + Theme.of(context).textTheme.bodyLarge, + ), + ), + Expanded(child: colorPickerSlider(trackType)), + if (widget.showParams) + ConstrainedBox( + constraints: BoxConstraints(minWidth: fontSize * 2 + 5), + child: Text( + getColorParams(trackTypes.indexOf(trackType)), + style: widget.sliderTextStyle ?? + Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.right, + ), + ), + ], + ), + ), + ]; + + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + if (widget.showIndicator) indicator(), + if (!widget.showIndicator) const SizedBox(height: 20), + ...sliders, + const SizedBox(height: 20.0), + if (widget.showLabel && widget.labelTypes.isNotEmpty) + Padding( + padding: const EdgeInsets.only(bottom: 20.0), + child: ColorPickerLabel( + currentHsvColor, + enableAlpha: widget.enableAlpha, + textStyle: widget.labelTextStyle, + colorLabelTypes: widget.labelTypes, + ), + ), + ], + ); + } +} + +/// The Color Picker with HUE Ring & HSV model. +class HueRingPicker extends StatefulWidget { + const HueRingPicker({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + this.portraitOnly = false, + this.colorPickerHeight = 250.0, + this.hueRingStrokeWidth = 20.0, + this.enableAlpha = false, + this.displayThumbColor = true, + this.pickerAreaBorderRadius = const BorderRadius.all(Radius.zero), + }) : super(key: key); + + final Color pickerColor; + final ValueChanged onColorChanged; + final bool portraitOnly; + final double colorPickerHeight; + final double hueRingStrokeWidth; + final bool enableAlpha; + final bool displayThumbColor; + final BorderRadius pickerAreaBorderRadius; + + @override + _HueRingPickerState createState() => _HueRingPickerState(); +} + +class _HueRingPickerState extends State { + HSVColor currentHsvColor = const HSVColor.fromAHSV(0.0, 0.0, 0.0, 0.0); + + @override + void initState() { + currentHsvColor = HSVColor.fromColor(widget.pickerColor); + super.initState(); + } + + @override + void didUpdateWidget(HueRingPicker oldWidget) { + super.didUpdateWidget(oldWidget); + currentHsvColor = HSVColor.fromColor(widget.pickerColor); + } + + void onColorChanging(HSVColor color) { + setState(() => currentHsvColor = color); + widget.onColorChanged(currentHsvColor.toColor()); + } + + @override + Widget build(BuildContext context) { + if (MediaQuery.of(context).orientation == Orientation.portrait || + widget.portraitOnly) { + return Column( + children: [ + ClipRRect( + borderRadius: widget.pickerAreaBorderRadius, + child: Padding( + padding: const EdgeInsets.all(15), + child: Stack( + alignment: AlignmentDirectional.center, + children: [ + SizedBox( + width: widget.colorPickerHeight, + height: widget.colorPickerHeight, + child: ColorPickerHueRing( + currentHsvColor, + onColorChanging, + displayThumbColor: widget.displayThumbColor, + strokeWidth: widget.hueRingStrokeWidth, + ), + ), + SizedBox( + width: widget.colorPickerHeight / 1.6, + height: widget.colorPickerHeight / 1.6, + child: ColorPickerArea( + currentHsvColor, onColorChanging, PaletteType.hsv), + ) + ]), + ), + ), + if (widget.enableAlpha) + SizedBox( + height: 40.0, + width: widget.colorPickerHeight, + child: ColorPickerSlider( + TrackType.alpha, + currentHsvColor, + onColorChanging, + displayThumbColor: widget.displayThumbColor, + ), + ), + Padding( + padding: const EdgeInsets.fromLTRB(15.0, 5.0, 10.0, 5.0), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const SizedBox(width: 10), + ColorIndicator(currentHsvColor), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(0, 5, 0, 20), + child: ColorPickerInput( + currentHsvColor.toColor(), + (Color color) { + setState( + () => currentHsvColor = HSVColor.fromColor(color)); + widget.onColorChanged(currentHsvColor.toColor()); + }, + enableAlpha: widget.enableAlpha, + embeddedText: true, + ), + ), + ), + ], + ), + ), + ], + ); + } else { + return Row( + children: [ + Expanded( + child: SizedBox( + width: 300.0, + height: widget.colorPickerHeight, + child: ClipRRect( + borderRadius: widget.pickerAreaBorderRadius, + child: ColorPickerArea( + currentHsvColor, onColorChanging, PaletteType.hsv), + ), + ), + ), + ClipRRect( + borderRadius: widget.pickerAreaBorderRadius, + child: Padding( + padding: const EdgeInsets.all(15), + child: Stack( + alignment: AlignmentDirectional.topCenter, + children: [ + SizedBox( + width: widget.colorPickerHeight - + widget.hueRingStrokeWidth * 2, + height: widget.colorPickerHeight - + widget.hueRingStrokeWidth * 2, + child: ColorPickerHueRing( + currentHsvColor, onColorChanging, + strokeWidth: widget.hueRingStrokeWidth), + ), + Column( + children: [ + SizedBox(height: widget.colorPickerHeight / 8.5), + ColorIndicator(currentHsvColor), + const SizedBox(height: 10), + ColorPickerInput( + currentHsvColor.toColor(), + (Color color) { + setState(() => + currentHsvColor = HSVColor.fromColor(color)); + widget.onColorChanged(currentHsvColor.toColor()); + }, + enableAlpha: widget.enableAlpha, + embeddedText: true, + disable: true, + ), + if (widget.enableAlpha) const SizedBox(height: 5), + if (widget.enableAlpha) + SizedBox( + height: 40.0, + width: (widget.colorPickerHeight - + widget.hueRingStrokeWidth * 2) / + 2, + child: ColorPickerSlider( + TrackType.alpha, + currentHsvColor, + onColorChanging, + displayThumbColor: true, + ), + ), + ], + ), + ]), + ), + ), + ], + ); + } + } +} diff --git a/lib/src/packages/flutter_colorpicker/src/colors.dart b/lib/src/packages/flutter_colorpicker/src/colors.dart new file mode 100644 index 00000000..041a1f20 --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/colors.dart @@ -0,0 +1,172 @@ +// ignore_for_file: type=lint + +import 'dart:ui'; + +/// X11 Colors +/// +/// https://en.wikipedia.org/wiki/X11_color_names + +const Map x11Colors = { + 'aliceblue': Color(0xfff0f8ff), + 'antiquewhite': Color(0xfffaebd7), + 'aqua': Color(0xff00ffff), + 'aquamarine': Color(0xff7fffd4), + 'azure': Color(0xfff0ffff), + 'beige': Color(0xfff5f5dc), + 'bisque': Color(0xffffe4c4), + 'black': Color(0xff000000), + 'blanchedalmond': Color(0xffffebcd), + 'blue': Color(0xff0000ff), + 'blueviolet': Color(0xff8a2be2), + 'brown': Color(0xffa52a2a), + 'burlywood': Color(0xffdeb887), + 'cadetblue': Color(0xff5f9ea0), + 'chartreuse': Color(0xff7fff00), + 'chocolate': Color(0xffd2691e), + 'coral': Color(0xffff7f50), + 'cornflower': Color(0xff6495ed), + 'cornflowerblue': Color(0xff6495ed), + 'cornsilk': Color(0xfffff8dc), + 'crimson': Color(0xffdc143c), + 'cyan': Color(0xff00ffff), + 'darkblue': Color(0xff00008b), + 'darkcyan': Color(0xff008b8b), + 'darkgoldenrod': Color(0xffb8860b), + 'darkgray': Color(0xffa9a9a9), + 'darkgreen': Color(0xff006400), + 'darkgrey': Color(0xffa9a9a9), + 'darkkhaki': Color(0xffbdb76b), + 'darkmagenta': Color(0xff8b008b), + 'darkolivegreen': Color(0xff556b2f), + 'darkorange': Color(0xffff8c00), + 'darkorchid': Color(0xff9932cc), + 'darkred': Color(0xff8b0000), + 'darksalmon': Color(0xffe9967a), + 'darkseagreen': Color(0xff8fbc8f), + 'darkslateblue': Color(0xff483d8b), + 'darkslategray': Color(0xff2f4f4f), + 'darkslategrey': Color(0xff2f4f4f), + 'darkturquoise': Color(0xff00ced1), + 'darkviolet': Color(0xff9400d3), + 'deeppink': Color(0xffff1493), + 'deepskyblue': Color(0xff00bfff), + 'dimgray': Color(0xff696969), + 'dimgrey': Color(0xff696969), + 'dodgerblue': Color(0xff1e90ff), + 'firebrick': Color(0xffb22222), + 'floralwhite': Color(0xfffffaf0), + 'forestgreen': Color(0xff228b22), + 'fuchsia': Color(0xffff00ff), + 'gainsboro': Color(0xffdcdcdc), + 'ghostwhite': Color(0xfff8f8ff), + 'gold': Color(0xffffd700), + 'goldenrod': Color(0xffdaa520), + 'gray': Color(0xff808080), + 'green': Color(0xff008000), + 'greenyellow': Color(0xffadff2f), + 'grey': Color(0xff808080), + 'honeydew': Color(0xfff0fff0), + 'hotpink': Color(0xffff69b4), + 'indianred': Color(0xffcd5c5c), + 'indigo': Color(0xff4b0082), + 'ivory': Color(0xfffffff0), + 'khaki': Color(0xfff0e68c), + 'laserlemon': Color(0xffffff54), + 'lavender': Color(0xffe6e6fa), + 'lavenderblush': Color(0xfffff0f5), + 'lawngreen': Color(0xff7cfc00), + 'lemonchiffon': Color(0xfffffacd), + 'lightblue': Color(0xffadd8e6), + 'lightcoral': Color(0xfff08080), + 'lightcyan': Color(0xffe0ffff), + 'lightgoldenrod': Color(0xfffafad2), + 'lightgoldenrodyellow': Color(0xfffafad2), + 'lightgray': Color(0xffd3d3d3), + 'lightgreen': Color(0xff90ee90), + 'lightgrey': Color(0xffd3d3d3), + 'lightpink': Color(0xffffb6c1), + 'lightsalmon': Color(0xffffa07a), + 'lightseagreen': Color(0xff20b2aa), + 'lightskyblue': Color(0xff87cefa), + 'lightslategray': Color(0xff778899), + 'lightslategrey': Color(0xff778899), + 'lightsteelblue': Color(0xffb0c4de), + 'lightyellow': Color(0xffffffe0), + 'lime': Color(0xff00ff00), + 'limegreen': Color(0xff32cd32), + 'linen': Color(0xfffaf0e6), + 'magenta': Color(0xffff00ff), + 'maroon': Color(0xff800000), + 'maroon2': Color(0xff7f0000), + 'maroon3': Color(0xffb03060), + 'mediumaquamarine': Color(0xff66cdaa), + 'mediumblue': Color(0xff0000cd), + 'mediumorchid': Color(0xffba55d3), + 'mediumpurple': Color(0xff9370db), + 'mediumseagreen': Color(0xff3cb371), + 'mediumslateblue': Color(0xff7b68ee), + 'mediumspringgreen': Color(0xff00fa9a), + 'mediumturquoise': Color(0xff48d1cc), + 'mediumvioletred': Color(0xffc71585), + 'midnightblue': Color(0xff191970), + 'mintcream': Color(0xfff5fffa), + 'mistyrose': Color(0xffffe4e1), + 'moccasin': Color(0xffffe4b5), + 'navajowhite': Color(0xffffdead), + 'navy': Color(0xff000080), + 'oldlace': Color(0xfffdf5e6), + 'olive': Color(0xff808000), + 'olivedrab': Color(0xff6b8e23), + 'orange': Color(0xffffa500), + 'orangered': Color(0xffff4500), + 'orchid': Color(0xffda70d6), + 'palegoldenrod': Color(0xffeee8aa), + 'palegreen': Color(0xff98fb98), + 'paleturquoise': Color(0xffafeeee), + 'palevioletred': Color(0xffdb7093), + 'papayawhip': Color(0xffffefd5), + 'peachpuff': Color(0xffffdab9), + 'peru': Color(0xffcd853f), + 'pink': Color(0xffffc0cb), + 'plum': Color(0xffdda0dd), + 'powderblue': Color(0xffb0e0e6), + 'purple': Color(0xff800080), + 'purple2': Color(0xff7f007f), + 'purple3': Color(0xffa020f0), + 'rebeccapurple': Color(0xff663399), + 'red': Color(0xffff0000), + 'rosybrown': Color(0xffbc8f8f), + 'royalblue': Color(0xff4169e1), + 'saddlebrown': Color(0xff8b4513), + 'salmon': Color(0xfffa8072), + 'sandybrown': Color(0xfff4a460), + 'seagreen': Color(0xff2e8b57), + 'seashell': Color(0xfffff5ee), + 'sienna': Color(0xffa0522d), + 'silver': Color(0xffc0c0c0), + 'skyblue': Color(0xff87ceeb), + 'slateblue': Color(0xff6a5acd), + 'slategray': Color(0xff708090), + 'slategrey': Color(0xff708090), + 'snow': Color(0xfffffafa), + 'springgreen': Color(0xff00ff7f), + 'steelblue': Color(0xff4682b4), + 'tan': Color(0xffd2b48c), + 'teal': Color(0xff008080), + 'thistle': Color(0xffd8bfd8), + 'tomato': Color(0xffff6347), + 'turquoise': Color(0xff40e0d0), + 'violet': Color(0xffee82ee), + 'wheat': Color(0xfff5deb3), + 'white': Color(0xffffffff), + 'whitesmoke': Color(0xfff5f5f5), + 'yellow': Color(0xffffff00), + 'yellowgreen': Color(0xff9acd32), +}; + +Color? colorFromName(String val) => + x11Colors[val.trim().replaceAll(' ', '').toLowerCase()]; + +extension ColorExtension on String { + Color? toColor() => colorFromName(this); +} diff --git a/lib/src/packages/flutter_colorpicker/src/material_picker.dart b/lib/src/packages/flutter_colorpicker/src/material_picker.dart new file mode 100644 index 00000000..b9d5d8c2 --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/material_picker.dart @@ -0,0 +1,384 @@ +// ignore_for_file: type=lint + +/// Material Color Picker + +library material_colorpicker; + +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'utils.dart'; + +// The Color Picker which contains Material Design Color Palette. +class MaterialPicker extends StatefulWidget { + const MaterialPicker({ + Key? key, + required this.pickerColor, + required this.onColorChanged, + this.onPrimaryChanged, + this.enableLabel = false, + this.portraitOnly = false, + }) : super(key: key); + + final Color pickerColor; + final ValueChanged onColorChanged; + final ValueChanged? onPrimaryChanged; + final bool enableLabel; + final bool portraitOnly; + + @override + State createState() => _MaterialPickerState(); +} + +class _MaterialPickerState extends State { + final List> _colorTypes = [ + [Colors.red, Colors.redAccent], + [Colors.pink, Colors.pinkAccent], + [Colors.purple, Colors.purpleAccent], + [Colors.deepPurple, Colors.deepPurpleAccent], + [Colors.indigo, Colors.indigoAccent], + [Colors.blue, Colors.blueAccent], + [Colors.lightBlue, Colors.lightBlueAccent], + [Colors.cyan, Colors.cyanAccent], + [Colors.teal, Colors.tealAccent], + [Colors.green, Colors.greenAccent], + [Colors.lightGreen, Colors.lightGreenAccent], + [Colors.lime, Colors.limeAccent], + [Colors.yellow, Colors.yellowAccent], + [Colors.amber, Colors.amberAccent], + [Colors.orange, Colors.orangeAccent], + [Colors.deepOrange, Colors.deepOrangeAccent], + [Colors.brown], + [Colors.grey], + [Colors.blueGrey], + [Colors.black], + ]; + + List _currentColorType = [Colors.red, Colors.redAccent]; + Color _currentShading = Colors.transparent; + + List> _shadingTypes(List colors) { + List> result = []; + + for (Color colorType in colors) { + if (colorType == Colors.grey) { + result.addAll([ + 50, + 100, + 200, + 300, + 350, + 400, + 500, + 600, + 700, + 800, + 850, + 900 + ].map((int shade) => {Colors.grey[shade]!: shade.toString()}).toList()); + } else if (colorType == Colors.black || colorType == Colors.white) { + result.addAll([ + {Colors.black: ''}, + {Colors.white: ''} + ]); + } else if (colorType is MaterialAccentColor) { + result.addAll([100, 200, 400, 700] + .map((int shade) => {colorType[shade]!: 'A$shade'}) + .toList()); + } else if (colorType is MaterialColor) { + result.addAll([50, 100, 200, 300, 400, 500, 600, 700, 800, 900] + .map((int shade) => {colorType[shade]!: shade.toString()}) + .toList()); + } else { + result.add({const Color(0x00000000): ''}); + } + } + + return result; + } + + @override + void initState() { + for (List _colors in _colorTypes) { + _shadingTypes(_colors).forEach((Map color) { + if (widget.pickerColor.value == color.keys.first.value) { + return setState(() { + _currentColorType = _colors; + _currentShading = color.keys.first; + }); + } + }); + } + super.initState(); + } + + @override + Widget build(BuildContext context) { + bool _isPortrait = + MediaQuery.of(context).orientation == Orientation.portrait || + widget.portraitOnly; + + Widget _colorList() { + return Container( + clipBehavior: Clip.hardEdge, + decoration: const BoxDecoration(), + child: Container( + margin: _isPortrait + ? const EdgeInsets.only(right: 10) + : const EdgeInsets.only(bottom: 10), + width: _isPortrait ? 60 : null, + height: _isPortrait ? null : 60, + decoration: BoxDecoration( + color: Theme.of(context).cardColor, + boxShadow: [ + BoxShadow( + color: (Theme.of(context).brightness == Brightness.light) + ? (Theme.of(context).brightness == Brightness.light) + ? Colors.grey[300]! + : Colors.black38 + : Colors.black38, + blurRadius: 10) + ], + border: _isPortrait + ? Border( + right: BorderSide( + color: + (Theme.of(context).brightness == Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + width: 1)) + : Border( + top: BorderSide( + color: + (Theme.of(context).brightness == Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + width: 1)), + ), + child: ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(dragDevices: PointerDeviceKind.values.toSet()), + child: ListView( + scrollDirection: _isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + _isPortrait + ? const Padding(padding: EdgeInsets.only(top: 7)) + : const Padding(padding: EdgeInsets.only(left: 7)), + ..._colorTypes.map((List _colors) { + Color _colorType = _colors[0]; + return GestureDetector( + onTap: () { + if (widget.onPrimaryChanged != null) + widget.onPrimaryChanged!(_colorType); + setState(() => _currentColorType = _colors); + }, + child: Container( + color: const Color(0x00000000), + padding: _isPortrait + ? const EdgeInsets.fromLTRB(0, 7, 0, 7) + : const EdgeInsets.fromLTRB(7, 0, 7, 0), + child: Align( + child: AnimatedContainer( + duration: const Duration(milliseconds: 300), + width: 25, + height: 25, + decoration: BoxDecoration( + color: _colorType, + shape: BoxShape.circle, + boxShadow: _currentColorType == _colors + ? [ + _colorType == Theme.of(context).cardColor + ? BoxShadow( + color: + (Theme.of(context).brightness == + Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + blurRadius: 10, + ) + : BoxShadow( + color: _colorType, + blurRadius: 10, + ), + ] + : null, + border: _colorType == Theme.of(context).cardColor + ? Border.all( + color: (Theme.of(context).brightness == + Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + width: 1) + : null, + ), + ), + ), + ), + ); + }), + _isPortrait + ? const Padding(padding: EdgeInsets.only(top: 5)) + : const Padding(padding: EdgeInsets.only(left: 5)), + ], + ), + ), + ), + ); + } + + Widget _shadingList() { + return ScrollConfiguration( + behavior: ScrollConfiguration.of(context) + .copyWith(dragDevices: PointerDeviceKind.values.toSet()), + child: ListView( + scrollDirection: _isPortrait ? Axis.vertical : Axis.horizontal, + children: [ + _isPortrait + ? const Padding(padding: EdgeInsets.only(top: 15)) + : const Padding(padding: EdgeInsets.only(left: 15)), + ..._shadingTypes(_currentColorType).map((Map color) { + final Color _color = color.keys.first; + return GestureDetector( + onTap: () { + setState(() => _currentShading = _color); + widget.onColorChanged(_color); + }, + child: Container( + color: const Color(0x00000000), + margin: _isPortrait + ? const EdgeInsets.only(right: 10) + : const EdgeInsets.only(bottom: 10), + padding: _isPortrait + ? const EdgeInsets.fromLTRB(0, 7, 0, 7) + : const EdgeInsets.fromLTRB(7, 0, 7, 0), + child: Align( + child: AnimatedContainer( + curve: Curves.fastOutSlowIn, + duration: const Duration(milliseconds: 500), + width: _isPortrait + ? (_currentShading == _color ? 250 : 230) + : (_currentShading == _color ? 50 : 30), + height: _isPortrait ? 50 : 220, + decoration: BoxDecoration( + color: _color, + boxShadow: _currentShading == _color + ? [ + (_color == Colors.white) || + (_color == Colors.black) + ? BoxShadow( + color: (Theme.of(context).brightness == + Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + blurRadius: 10, + ) + : BoxShadow( + color: _color, + blurRadius: 10, + ), + ] + : null, + border: + (_color == Colors.white) || (_color == Colors.black) + ? Border.all( + color: (Theme.of(context).brightness == + Brightness.light) + ? Colors.grey[300]! + : Colors.black38, + width: 1) + : null, + ), + child: widget.enableLabel + ? _isPortrait + ? Row( + children: [ + Text( + ' ${color.values.first}', + style: TextStyle( + color: useWhiteForeground(_color) + ? Colors.white + : Colors.black), + ), + Expanded( + child: Align( + alignment: Alignment.centerRight, + child: Text( + '#${(_color.toString().replaceFirst('Color(0xff', '').replaceFirst(')', '')).toUpperCase()} ', + style: TextStyle( + color: useWhiteForeground(_color) + ? Colors.white + : Colors.black, + fontWeight: FontWeight.bold, + ), + ), + ), + ), + ], + ) + : AnimatedOpacity( + duration: const Duration(milliseconds: 300), + opacity: _currentShading == _color ? 1 : 0, + child: Container( + padding: const EdgeInsets.only(top: 16), + alignment: Alignment.topCenter, + child: Text( + color.values.first, + style: TextStyle( + color: useWhiteForeground(_color) + ? Colors.white + : Colors.black, + fontWeight: FontWeight.bold, + fontSize: 14, + ), + softWrap: false, + ), + ), + ) + : const SizedBox(), + ), + ), + ), + ); + }), + _isPortrait + ? const Padding(padding: EdgeInsets.only(top: 15)) + : const Padding(padding: EdgeInsets.only(left: 15)), + ], + ), + ); + } + + if (_isPortrait) { + return SizedBox( + width: 350, + height: 500, + child: Row( + children: [ + _colorList(), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: _shadingList(), + ), + ), + ], + ), + ); + } else { + return SizedBox( + width: 500, + height: 300, + child: Column( + children: [ + _colorList(), + Expanded( + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 12), + child: _shadingList(), + ), + ), + ], + ), + ); + } + } +} diff --git a/lib/src/packages/flutter_colorpicker/src/palette.dart b/lib/src/packages/flutter_colorpicker/src/palette.dart new file mode 100644 index 00000000..5dc7c4ec --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/palette.dart @@ -0,0 +1,1523 @@ +// ignore_for_file: type=lint + +/// The components of HSV Color Picker +/// +/// Try to create a Color Picker with other layout on your own :) + +import 'dart:math'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'utils.dart'; + +/// Palette types for color picker area widget. +enum PaletteType { + hsv, + hsvWithHue, + hsvWithValue, + hsvWithSaturation, + hsl, + hslWithHue, + hslWithLightness, + hslWithSaturation, + rgbWithBlue, + rgbWithGreen, + rgbWithRed, + hueWheel, +} + +/// Track types for slider picker. +enum TrackType { + hue, + saturation, + saturationForHSL, + value, + lightness, + red, + green, + blue, + alpha, +} + +/// Color information label type. +enum ColorLabelType { hex, rgb, hsv, hsl } + +/// Types for slider picker widget. +enum ColorModel { rgb, hsv, hsl } +// enum ColorSpace { rgb, hsv, hsl, hsp, okhsv, okhsl, xyz, yuv, lab, lch, cmyk } + +/// Painter for SV mixture. +class HSVWithHueColorPainter extends CustomPainter { + const HSVWithHueColorPainter(this.hsvColor, {this.pointerColor}); + + final HSVColor hsvColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.white, Colors.black], + ); + final Gradient gradientH = LinearGradient( + colors: [ + Colors.white, + HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(), + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + canvas.drawRect( + rect, + Paint() + ..blendMode = BlendMode.multiply + ..shader = gradientH.createShader(rect), + ); + + canvas.drawCircle( + Offset( + size.width * hsvColor.saturation, size.height * (1 - hsvColor.value)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hsvColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..blendMode = BlendMode.luminosity + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for HV mixture. +class HSVWithSaturationColorPainter extends CustomPainter { + const HSVWithSaturationColorPainter(this.hsvColor, {this.pointerColor}); + + final HSVColor hsvColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.black], + ); + final List colors = [ + HSVColor.fromAHSV(1.0, 0.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 60.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 120.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 180.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 240.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 300.0, hsvColor.saturation, 1.0).toColor(), + HSVColor.fromAHSV(1.0, 360.0, hsvColor.saturation, 1.0).toColor(), + ]; + final Gradient gradientH = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + + canvas.drawCircle( + Offset( + size.width * hsvColor.hue / 360, + size.height * (1 - hsvColor.value), + ), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hsvColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for HS mixture. +class HSVWithValueColorPainter extends CustomPainter { + const HSVWithValueColorPainter(this.hsvColor, {this.pointerColor}); + + final HSVColor hsvColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [Colors.transparent, Colors.white], + ); + final List colors = [ + const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 60.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 120.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 180.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 240.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 300.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0).toColor(), + ]; + final Gradient gradientH = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + canvas.drawRect( + rect, + Paint()..color = Colors.black.withOpacity(1 - hsvColor.value), + ); + + canvas.drawCircle( + Offset( + size.width * hsvColor.hue / 360, + size.height * (1 - hsvColor.saturation), + ), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hsvColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for SL mixture. +class HSLWithHueColorPainter extends CustomPainter { + const HSLWithHueColorPainter(this.hslColor, {this.pointerColor}); + + final HSLColor hslColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final Gradient gradientH = LinearGradient( + colors: [ + const Color(0xff808080), + HSLColor.fromAHSL(1.0, hslColor.hue, 1.0, 0.5).toColor(), + ], + ); + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0.0, 0.5, 0.5, 1], + colors: [ + Colors.white, + Color(0x00ffffff), + Colors.transparent, + Colors.black, + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + + canvas.drawCircle( + Offset(size.width * hslColor.saturation, + size.height * (1 - hslColor.lightness)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hslColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for HL mixture. +class HSLWithSaturationColorPainter extends CustomPainter { + const HSLWithSaturationColorPainter(this.hslColor, {this.pointerColor}); + + final HSLColor hslColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final List colors = [ + HSLColor.fromAHSL(1.0, 0.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 60.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 120.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 180.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 240.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 300.0, hslColor.saturation, 0.5).toColor(), + HSLColor.fromAHSL(1.0, 360.0, hslColor.saturation, 0.5).toColor(), + ]; + final Gradient gradientH = LinearGradient(colors: colors); + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + stops: [0.0, 0.5, 0.5, 1], + colors: [ + Colors.white, + Color(0x00ffffff), + Colors.transparent, + Colors.black, + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + + canvas.drawCircle( + Offset(size.width * hslColor.hue / 360, + size.height * (1 - hslColor.lightness)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hslColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for HS mixture. +class HSLWithLightnessColorPainter extends CustomPainter { + const HSLWithLightnessColorPainter(this.hslColor, {this.pointerColor}); + + final HSLColor hslColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final List colors = [ + const HSLColor.fromAHSL(1.0, 0.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 60.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 120.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 180.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 240.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 300.0, 1.0, 0.5).toColor(), + const HSLColor.fromAHSL(1.0, 360.0, 1.0, 0.5).toColor(), + ]; + final Gradient gradientH = LinearGradient(colors: colors); + const Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Color(0xFF808080), + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect(rect, Paint()..shader = gradientV.createShader(rect)); + canvas.drawRect( + rect, + Paint() + ..color = + Colors.black.withOpacity((1 - hslColor.lightness * 2).clamp(0, 1)), + ); + canvas.drawRect( + rect, + Paint() + ..color = Colors.white + .withOpacity(((hslColor.lightness - 0.5) * 2).clamp(0, 1)), + ); + + canvas.drawCircle( + Offset(size.width * hslColor.hue / 360, + size.height * (1 - hslColor.saturation)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hslColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for GB mixture. +class RGBWithRedColorPainter extends CustomPainter { + const RGBWithRedColorPainter(this.color, {this.pointerColor}); + + final Color color; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final Gradient gradientH = LinearGradient( + colors: [ + Color.fromRGBO(color.red, 255, 0, 1.0), + Color.fromRGBO(color.red, 255, 255, 1.0), + ], + ); + final Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color.fromRGBO(color.red, 255, 255, 1.0), + Color.fromRGBO(color.red, 0, 255, 1.0), + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect( + rect, + Paint() + ..shader = gradientV.createShader(rect) + ..blendMode = BlendMode.multiply, + ); + + canvas.drawCircle( + Offset( + size.width * color.blue / 255, size.height * (1 - color.green / 255)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(color) ? Colors.white : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for RB mixture. +class RGBWithGreenColorPainter extends CustomPainter { + const RGBWithGreenColorPainter(this.color, {this.pointerColor}); + + final Color color; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final Gradient gradientH = LinearGradient( + colors: [ + Color.fromRGBO(255, color.green, 0, 1.0), + Color.fromRGBO(255, color.green, 255, 1.0), + ], + ); + final Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color.fromRGBO(255, color.green, 255, 1.0), + Color.fromRGBO(0, color.green, 255, 1.0), + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect( + rect, + Paint() + ..shader = gradientV.createShader(rect) + ..blendMode = BlendMode.multiply, + ); + + canvas.drawCircle( + Offset( + size.width * color.blue / 255, size.height * (1 - color.red / 255)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(color) ? Colors.white : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for RG mixture. +class RGBWithBlueColorPainter extends CustomPainter { + const RGBWithBlueColorPainter(this.color, {this.pointerColor}); + + final Color color; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + final Gradient gradientH = LinearGradient( + colors: [ + Color.fromRGBO(0, 255, color.blue, 1.0), + Color.fromRGBO(255, 255, color.blue, 1.0), + ], + ); + final Gradient gradientV = LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Color.fromRGBO(255, 255, color.blue, 1.0), + Color.fromRGBO(255, 0, color.blue, 1.0), + ], + ); + canvas.drawRect(rect, Paint()..shader = gradientH.createShader(rect)); + canvas.drawRect( + rect, + Paint() + ..shader = gradientV.createShader(rect) + ..blendMode = BlendMode.multiply, + ); + + canvas.drawCircle( + Offset( + size.width * color.red / 255, size.height * (1 - color.green / 255)), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(color) ? Colors.white : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for hue color wheel. +class HUEColorWheelPainter extends CustomPainter { + const HUEColorWheelPainter(this.hsvColor, {this.pointerColor}); + + final HSVColor hsvColor; + final Color? pointerColor; + + @override + void paint(Canvas canvas, Size size) { + Rect rect = Offset.zero & size; + Offset center = Offset(size.width / 2, size.height / 2); + double radio = size.width <= size.height ? size.width / 2 : size.height / 2; + + final List colors = [ + const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 300.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 240.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 180.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 120.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 60.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0).toColor(), + ]; + final Gradient gradientS = SweepGradient(colors: colors); + const Gradient gradientR = RadialGradient( + colors: [ + Colors.white, + Color(0x00FFFFFF), + ], + ); + canvas.drawCircle( + center, radio, Paint()..shader = gradientS.createShader(rect)); + canvas.drawCircle( + center, radio, Paint()..shader = gradientR.createShader(rect)); + canvas.drawCircle(center, radio, + Paint()..color = Colors.black.withOpacity(1 - hsvColor.value)); + + canvas.drawCircle( + Offset( + center.dx + + hsvColor.saturation * radio * cos((hsvColor.hue * pi / 180)), + center.dy - + hsvColor.saturation * radio * sin((hsvColor.hue * pi / 180)), + ), + size.height * 0.04, + Paint() + ..color = pointerColor ?? + (useWhiteForeground(hsvColor.toColor()) + ? Colors.white + : Colors.black) + ..strokeWidth = 1.5 + ..style = PaintingStyle.stroke, + ); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for hue ring. +class HueRingPainter extends CustomPainter { + const HueRingPainter(this.hsvColor, + {this.displayThumbColor = true, this.strokeWidth = 5}); + + final HSVColor hsvColor; + final bool displayThumbColor; + final double strokeWidth; + + @override + void paint(Canvas canvas, Size size) { + Rect rect = Offset.zero & size; + Offset center = Offset(size.width / 2, size.height / 2); + double radio = size.width <= size.height ? size.width / 2 : size.height / 2; + + final List colors = [ + const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 300.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 240.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 180.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 120.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 60.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0).toColor(), + ]; + canvas.drawCircle( + center, + radio, + Paint() + ..shader = SweepGradient(colors: colors).createShader(rect) + ..style = PaintingStyle.stroke + ..strokeWidth = strokeWidth, + ); + + final Offset offset = Offset( + center.dx + radio * cos((hsvColor.hue * pi / 180)), + center.dy - radio * sin((hsvColor.hue * pi / 180)), + ); + canvas.drawShadow( + Path()..addOval(Rect.fromCircle(center: offset, radius: 12)), + Colors.black, + 3.0, + true); + canvas.drawCircle( + offset, + size.height * 0.04, + Paint() + ..color = Colors.white + ..style = PaintingStyle.fill, + ); + if (displayThumbColor) { + canvas.drawCircle( + offset, + size.height * 0.03, + Paint() + ..color = hsvColor.toColor() + ..style = PaintingStyle.fill, + ); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +class _SliderLayout extends MultiChildLayoutDelegate { + static const String track = 'track'; + static const String thumb = 'thumb'; + static const String gestureContainer = 'gesturecontainer'; + + @override + void performLayout(Size size) { + layoutChild( + track, + BoxConstraints.tightFor( + width: size.width - 30.0, + height: size.height / 5, + ), + ); + positionChild(track, Offset(15.0, size.height * 0.4)); + layoutChild( + thumb, + BoxConstraints.tightFor(width: 5.0, height: size.height / 4), + ); + positionChild(thumb, Offset(0.0, size.height * 0.4)); + layoutChild( + gestureContainer, + BoxConstraints.tightFor(width: size.width, height: size.height), + ); + positionChild(gestureContainer, Offset.zero); + } + + @override + bool shouldRelayout(_SliderLayout oldDelegate) => false; +} + +/// Painter for all kinds of track types. +class TrackPainter extends CustomPainter { + const TrackPainter(this.trackType, this.hsvColor); + + final TrackType trackType; + final HSVColor hsvColor; + + @override + void paint(Canvas canvas, Size size) { + final Rect rect = Offset.zero & size; + if (trackType == TrackType.alpha) { + final Size chessSize = Size(size.height / 2, size.height / 2); + Paint chessPaintB = Paint()..color = const Color(0xffcccccc); + Paint chessPaintW = Paint()..color = Colors.white; + List.generate((size.height / chessSize.height).round(), (int y) { + List.generate((size.width / chessSize.width).round(), (int x) { + canvas.drawRect( + Offset(chessSize.width * x, chessSize.width * y) & chessSize, + (x + y) % 2 != 0 ? chessPaintW : chessPaintB, + ); + }); + }); + } + + switch (trackType) { + case TrackType.hue: + final List colors = [ + const HSVColor.fromAHSV(1.0, 0.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 60.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 120.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 180.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 240.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 300.0, 1.0, 1.0).toColor(), + const HSVColor.fromAHSV(1.0, 360.0, 1.0, 1.0).toColor(), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.saturation: + final List colors = [ + HSVColor.fromAHSV(1.0, hsvColor.hue, 0.0, 1.0).toColor(), + HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.saturationForHSL: + final List colors = [ + HSLColor.fromAHSL(1.0, hsvColor.hue, 0.0, 0.5).toColor(), + HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.5).toColor(), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.value: + final List colors = [ + HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 0.0).toColor(), + HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.lightness: + final List colors = [ + HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.0).toColor(), + HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 0.5).toColor(), + HSLColor.fromAHSL(1.0, hsvColor.hue, 1.0, 1.0).toColor(), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.red: + final List colors = [ + hsvColor.toColor().withRed(0).withOpacity(1.0), + hsvColor.toColor().withRed(255).withOpacity(1.0), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.green: + final List colors = [ + hsvColor.toColor().withGreen(0).withOpacity(1.0), + hsvColor.toColor().withGreen(255).withOpacity(1.0), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.blue: + final List colors = [ + hsvColor.toColor().withBlue(0).withOpacity(1.0), + hsvColor.toColor().withBlue(255).withOpacity(1.0), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + case TrackType.alpha: + final List colors = [ + hsvColor.toColor().withOpacity(0.0), + hsvColor.toColor().withOpacity(1.0), + ]; + Gradient gradient = LinearGradient(colors: colors); + canvas.drawRect(rect, Paint()..shader = gradient.createShader(rect)); + break; + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for thumb of slider. +class ThumbPainter extends CustomPainter { + const ThumbPainter({this.thumbColor, this.fullThumbColor = false}); + + final Color? thumbColor; + final bool fullThumbColor; + + @override + void paint(Canvas canvas, Size size) { + canvas.drawShadow( + Path() + ..addOval( + Rect.fromCircle( + center: const Offset(0.5, 2.0), radius: size.width * 1.8), + ), + Colors.black, + 3.0, + true, + ); + canvas.drawCircle( + Offset(0.0, size.height * 0.4), + size.height, + Paint() + ..color = Colors.white + ..style = PaintingStyle.fill); + if (thumbColor != null) { + canvas.drawCircle( + Offset(0.0, size.height * 0.4), + size.height * (fullThumbColor ? 1.0 : 0.65), + Paint() + ..color = thumbColor! + ..style = PaintingStyle.fill); + } + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for chess type alpha background in color indicator widget. +class IndicatorPainter extends CustomPainter { + const IndicatorPainter(this.color); + + final Color color; + + @override + void paint(Canvas canvas, Size size) { + final Size chessSize = Size(size.width / 10, size.height / 10); + final Paint chessPaintB = Paint()..color = const Color(0xFFCCCCCC); + final Paint chessPaintW = Paint()..color = Colors.white; + List.generate((size.height / chessSize.height).round(), (int y) { + List.generate((size.width / chessSize.width).round(), (int x) { + canvas.drawRect( + Offset(chessSize.width * x, chessSize.height * y) & chessSize, + (x + y) % 2 != 0 ? chessPaintW : chessPaintB, + ); + }); + }); + + canvas.drawCircle( + Offset(size.width / 2, size.height / 2), + size.height / 2, + Paint() + ..color = color + ..style = PaintingStyle.fill); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Painter for chess type alpha background in slider track widget. +class CheckerPainter extends CustomPainter { + const CheckerPainter(); + + @override + void paint(Canvas canvas, Size size) { + final Size chessSize = Size(size.height / 6, size.height / 6); + Paint chessPaintB = Paint()..color = const Color(0xffcccccc); + Paint chessPaintW = Paint()..color = Colors.white; + List.generate((size.height / chessSize.height).round(), (int y) { + List.generate((size.width / chessSize.width).round(), (int x) { + canvas.drawRect( + Offset(chessSize.width * x, chessSize.width * y) & chessSize, + (x + y) % 2 != 0 ? chessPaintW : chessPaintB, + ); + }); + }); + } + + @override + bool shouldRepaint(CustomPainter oldDelegate) => false; +} + +/// Provide label for color information. +class ColorPickerLabel extends StatefulWidget { + const ColorPickerLabel( + this.hsvColor, { + Key? key, + this.enableAlpha = true, + this.colorLabelTypes = const [ + ColorLabelType.rgb, + ColorLabelType.hsv, + ColorLabelType.hsl + ], + this.textStyle, + }) : assert(colorLabelTypes.length > 0), + super(key: key); + + final HSVColor hsvColor; + final bool enableAlpha; + final TextStyle? textStyle; + final List colorLabelTypes; + + @override + _ColorPickerLabelState createState() => _ColorPickerLabelState(); +} + +class _ColorPickerLabelState extends State { + final Map> _colorTypes = const { + ColorLabelType.hex: ['R', 'G', 'B', 'A'], + ColorLabelType.rgb: ['R', 'G', 'B', 'A'], + ColorLabelType.hsv: ['H', 'S', 'V', 'A'], + ColorLabelType.hsl: ['H', 'S', 'L', 'A'], + }; + + late ColorLabelType _colorType; + + @override + void initState() { + super.initState(); + _colorType = widget.colorLabelTypes[0]; + } + + List colorValue(HSVColor hsvColor, ColorLabelType colorLabelType) { + if (colorLabelType == ColorLabelType.hex) { + final Color color = hsvColor.toColor(); + return [ + color.red.toRadixString(16).toUpperCase().padLeft(2, '0'), + color.green.toRadixString(16).toUpperCase().padLeft(2, '0'), + color.blue.toRadixString(16).toUpperCase().padLeft(2, '0'), + color.alpha.toRadixString(16).toUpperCase().padLeft(2, '0'), + ]; + } else if (colorLabelType == ColorLabelType.rgb) { + final Color color = hsvColor.toColor(); + return [ + color.red.toString(), + color.green.toString(), + color.blue.toString(), + '${(color.opacity * 100).round()}%', + ]; + } else if (colorLabelType == ColorLabelType.hsv) { + return [ + '${hsvColor.hue.round()}°', + '${(hsvColor.saturation * 100).round()}%', + '${(hsvColor.value * 100).round()}%', + '${(hsvColor.alpha * 100).round()}%', + ]; + } else if (colorLabelType == ColorLabelType.hsl) { + HSLColor hslColor = hsvToHsl(hsvColor); + return [ + '${hslColor.hue.round()}°', + '${(hslColor.saturation * 100).round()}%', + '${(hslColor.lightness * 100).round()}%', + '${(hsvColor.alpha * 100).round()}%', + ]; + } else { + return ['??', '??', '??', '??']; + } + } + + List colorValueLabels() { + double fontSize = 14; + if (widget.textStyle != null && widget.textStyle?.fontSize != null) + fontSize = widget.textStyle?.fontSize ?? 14; + + return [ + for (String item in _colorTypes[_colorType] ?? []) + if (widget.enableAlpha || item != 'A') + Padding( + padding: const EdgeInsets.symmetric(horizontal: 4.0), + child: ConstrainedBox( + constraints: BoxConstraints(minWidth: fontSize * 2), + child: IntrinsicHeight( + child: Column( + children: [ + Text( + item, + style: widget.textStyle ?? + Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: 10.0), + Expanded( + child: Text( + colorValue(widget.hsvColor, _colorType)[ + _colorTypes[_colorType]!.indexOf(item)], + overflow: TextOverflow.ellipsis, + style: widget.textStyle ?? + Theme.of(context).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ), + ) + ]; + } + + @override + Widget build(BuildContext context) { + return Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + DropdownButton( + value: _colorType, + onChanged: (ColorLabelType? type) { + if (type != null) setState(() => _colorType = type); + }, + items: [ + for (ColorLabelType type in widget.colorLabelTypes) + DropdownMenuItem( + value: type, + child: Text(type.toString().split('.').last.toUpperCase()), + ) + ], + ), + const SizedBox(width: 10.0), + ...colorValueLabels(), + ]); + } +} + +/// Provide hex input wiget for 3/6/8 digits. +class ColorPickerInput extends StatefulWidget { + const ColorPickerInput( + this.color, + this.onColorChanged, { + Key? key, + this.enableAlpha = true, + this.embeddedText = false, + this.disable = false, + }) : super(key: key); + + final Color color; + final ValueChanged onColorChanged; + final bool enableAlpha; + final bool embeddedText; + final bool disable; + + @override + _ColorPickerInputState createState() => _ColorPickerInputState(); +} + +class _ColorPickerInputState extends State { + TextEditingController textEditingController = TextEditingController(); + int inputColor = 0; + + @override + void dispose() { + textEditingController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + if (inputColor != widget.color.value) { + textEditingController.text = '#' + + widget.color.red.toRadixString(16).toUpperCase().padLeft(2, '0') + + widget.color.green.toRadixString(16).toUpperCase().padLeft(2, '0') + + widget.color.blue.toRadixString(16).toUpperCase().padLeft(2, '0') + + (widget.enableAlpha + ? widget.color.alpha + .toRadixString(16) + .toUpperCase() + .padLeft(2, '0') + : ''); + } + return Padding( + padding: const EdgeInsets.only(top: 5.0), + child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ + if (!widget.embeddedText) + Text('Hex', style: Theme.of(context).textTheme.bodyLarge), + const SizedBox(width: 10), + SizedBox( + width: (Theme.of(context).textTheme.bodyMedium?.fontSize ?? 14) * 10, + child: TextField( + enabled: !widget.disable, + controller: textEditingController, + inputFormatters: [ + UpperCaseTextFormatter(), + FilteringTextInputFormatter.allow(RegExp(kValidHexPattern)), + ], + decoration: InputDecoration( + isDense: true, + label: widget.embeddedText ? const Text('Hex') : null, + contentPadding: const EdgeInsets.symmetric(vertical: 5), + ), + onChanged: (String value) { + String input = value; + if (value.length == 9) { + input = value.split('').getRange(7, 9).join() + + value.split('').getRange(1, 7).join(); + } + final Color? color = colorFromHex(input); + if (color != null) { + widget.onColorChanged(color); + inputColor = color.value; + } + }, + ), + ), + ]), + ); + } +} + +/// 9 track types for slider picker widget. +class ColorPickerSlider extends StatelessWidget { + const ColorPickerSlider( + this.trackType, + this.hsvColor, + this.onColorChanged, { + Key? key, + this.displayThumbColor = false, + this.fullThumbColor = false, + }) : super(key: key); + + final TrackType trackType; + final HSVColor hsvColor; + final ValueChanged onColorChanged; + final bool displayThumbColor; + final bool fullThumbColor; + + void slideEvent(RenderBox getBox, BoxConstraints box, Offset globalPosition) { + double localDx = getBox.globalToLocal(globalPosition).dx - 15.0; + double progress = + localDx.clamp(0.0, box.maxWidth - 30.0) / (box.maxWidth - 30.0); + switch (trackType) { + case TrackType.hue: + // 360 is the same as zero + // if set to 360, sliding to end goes to zero + onColorChanged(hsvColor.withHue(progress * 359)); + break; + case TrackType.saturation: + onColorChanged(hsvColor.withSaturation(progress)); + break; + case TrackType.saturationForHSL: + onColorChanged(hslToHsv(hsvToHsl(hsvColor).withSaturation(progress))); + break; + case TrackType.value: + onColorChanged(hsvColor.withValue(progress)); + break; + case TrackType.lightness: + onColorChanged(hslToHsv(hsvToHsl(hsvColor).withLightness(progress))); + break; + case TrackType.red: + onColorChanged(HSVColor.fromColor( + hsvColor.toColor().withRed((progress * 0xff).round()))); + break; + case TrackType.green: + onColorChanged(HSVColor.fromColor( + hsvColor.toColor().withGreen((progress * 0xff).round()))); + break; + case TrackType.blue: + onColorChanged(HSVColor.fromColor( + hsvColor.toColor().withBlue((progress * 0xff).round()))); + break; + case TrackType.alpha: + onColorChanged(hsvColor.withAlpha( + localDx.clamp(0.0, box.maxWidth - 30.0) / (box.maxWidth - 30.0))); + break; + } + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (BuildContext context, BoxConstraints box) { + double thumbOffset = 15.0; + Color thumbColor; + switch (trackType) { + case TrackType.hue: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.hue / 360; + thumbColor = HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, 1.0).toColor(); + break; + case TrackType.saturation: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.saturation; + thumbColor = + HSVColor.fromAHSV(1.0, hsvColor.hue, hsvColor.saturation, 1.0) + .toColor(); + break; + case TrackType.saturationForHSL: + thumbOffset += (box.maxWidth - 30.0) * hsvToHsl(hsvColor).saturation; + thumbColor = HSLColor.fromAHSL( + 1.0, hsvColor.hue, hsvToHsl(hsvColor).saturation, 0.5) + .toColor(); + break; + case TrackType.value: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.value; + thumbColor = HSVColor.fromAHSV(1.0, hsvColor.hue, 1.0, hsvColor.value) + .toColor(); + break; + case TrackType.lightness: + thumbOffset += (box.maxWidth - 30.0) * hsvToHsl(hsvColor).lightness; + thumbColor = HSLColor.fromAHSL( + 1.0, hsvColor.hue, 1.0, hsvToHsl(hsvColor).lightness) + .toColor(); + break; + case TrackType.red: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().red / 0xff; + thumbColor = hsvColor.toColor().withOpacity(1.0); + break; + case TrackType.green: + thumbOffset += + (box.maxWidth - 30.0) * hsvColor.toColor().green / 0xff; + thumbColor = hsvColor.toColor().withOpacity(1.0); + break; + case TrackType.blue: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().blue / 0xff; + thumbColor = hsvColor.toColor().withOpacity(1.0); + break; + case TrackType.alpha: + thumbOffset += (box.maxWidth - 30.0) * hsvColor.toColor().opacity; + thumbColor = hsvColor.toColor().withOpacity(hsvColor.alpha); + break; + } + + return CustomMultiChildLayout( + delegate: _SliderLayout(), + children: [ + LayoutId( + id: _SliderLayout.track, + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(50.0)), + child: CustomPaint( + painter: TrackPainter( + trackType, + hsvColor, + )), + ), + ), + LayoutId( + id: _SliderLayout.thumb, + child: Transform.translate( + offset: Offset(thumbOffset, 0.0), + child: CustomPaint( + painter: ThumbPainter( + thumbColor: displayThumbColor ? thumbColor : null, + fullThumbColor: fullThumbColor, + ), + ), + ), + ), + LayoutId( + id: _SliderLayout.gestureContainer, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints box) { + RenderBox? getBox = context.findRenderObject() as RenderBox?; + return GestureDetector( + onPanDown: (DragDownDetails details) => getBox != null + ? slideEvent(getBox, box, details.globalPosition) + : null, + onPanUpdate: (DragUpdateDetails details) => getBox != null + ? slideEvent(getBox, box, details.globalPosition) + : null, + ); + }, + ), + ), + ], + ); + }); + } +} + +/// Simple round color indicator. +class ColorIndicator extends StatelessWidget { + const ColorIndicator( + this.hsvColor, { + Key? key, + this.width = 50.0, + this.height = 50.0, + }) : super(key: key); + + final HSVColor hsvColor; + final double width; + final double height; + + @override + Widget build(BuildContext context) { + return Container( + width: width, + height: height, + decoration: BoxDecoration( + borderRadius: const BorderRadius.all(Radius.circular(1000.0)), + border: Border.all(color: const Color(0xffdddddd)), + ), + child: ClipRRect( + borderRadius: const BorderRadius.all(Radius.circular(1000.0)), + child: CustomPaint(painter: IndicatorPainter(hsvColor.toColor())), + ), + ); + } +} + +/// Provide Rectangle & Circle 2 categories, 10 variations of palette widget. +class ColorPickerArea extends StatelessWidget { + const ColorPickerArea( + this.hsvColor, + this.onColorChanged, + this.paletteType, { + Key? key, + }) : super(key: key); + + final HSVColor hsvColor; + final ValueChanged onColorChanged; + final PaletteType paletteType; + + void _handleColorRectChange(double horizontal, double vertical) { + switch (paletteType) { + case PaletteType.hsv: + case PaletteType.hsvWithHue: + onColorChanged(hsvColor.withSaturation(horizontal).withValue(vertical)); + break; + case PaletteType.hsvWithSaturation: + onColorChanged(hsvColor.withHue(horizontal * 360).withValue(vertical)); + break; + case PaletteType.hsvWithValue: + onColorChanged( + hsvColor.withHue(horizontal * 360).withSaturation(vertical)); + break; + case PaletteType.hsl: + case PaletteType.hslWithHue: + onColorChanged(hslToHsv( + hsvToHsl(hsvColor).withSaturation(horizontal).withLightness(vertical), + )); + break; + case PaletteType.hslWithSaturation: + onColorChanged(hslToHsv( + hsvToHsl(hsvColor).withHue(horizontal * 360).withLightness(vertical), + )); + break; + case PaletteType.hslWithLightness: + onColorChanged(hslToHsv( + hsvToHsl(hsvColor).withHue(horizontal * 360).withSaturation(vertical), + )); + break; + case PaletteType.rgbWithRed: + onColorChanged(HSVColor.fromColor( + hsvColor + .toColor() + .withBlue((horizontal * 255).round()) + .withGreen((vertical * 255).round()), + )); + break; + case PaletteType.rgbWithGreen: + onColorChanged(HSVColor.fromColor( + hsvColor + .toColor() + .withBlue((horizontal * 255).round()) + .withRed((vertical * 255).round()), + )); + break; + case PaletteType.rgbWithBlue: + onColorChanged(HSVColor.fromColor( + hsvColor + .toColor() + .withRed((horizontal * 255).round()) + .withGreen((vertical * 255).round()), + )); + break; + default: + break; + } + } + + void _handleColorWheelChange(double hue, double radio) { + onColorChanged(hsvColor.withHue(hue).withSaturation(radio)); + } + + void _handleGesture( + Offset position, BuildContext context, double height, double width) { + RenderBox? getBox = context.findRenderObject() as RenderBox?; + if (getBox == null) return; + + Offset localOffset = getBox.globalToLocal(position); + double horizontal = localOffset.dx.clamp(0.0, width); + double vertical = localOffset.dy.clamp(0.0, height); + + if (paletteType == PaletteType.hueWheel) { + Offset center = Offset(width / 2, height / 2); + double radio = width <= height ? width / 2 : height / 2; + double dist = + sqrt(pow(horizontal - center.dx, 2) + pow(vertical - center.dy, 2)) / + radio; + double rad = + (atan2(horizontal - center.dx, vertical - center.dy) / pi + 1) / + 2 * + 360; + _handleColorWheelChange( + ((rad + 90) % 360).clamp(0, 360), dist.clamp(0, 1)); + } else { + _handleColorRectChange(horizontal / width, 1 - vertical / height); + } + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + double width = constraints.maxWidth; + double height = constraints.maxHeight; + + return RawGestureDetector( + gestures: { + _AlwaysWinPanGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + _AlwaysWinPanGestureRecognizer>( + () => _AlwaysWinPanGestureRecognizer(), + (_AlwaysWinPanGestureRecognizer instance) { + instance + ..onDown = ((details) => _handleGesture( + details.globalPosition, context, height, width)) + ..onUpdate = ((details) => _handleGesture( + details.globalPosition, context, height, width)); + }, + ), + }, + child: Builder( + builder: (BuildContext _) { + switch (paletteType) { + case PaletteType.hsv: + case PaletteType.hsvWithHue: + return CustomPaint(painter: HSVWithHueColorPainter(hsvColor)); + case PaletteType.hsvWithSaturation: + return CustomPaint( + painter: HSVWithSaturationColorPainter(hsvColor)); + case PaletteType.hsvWithValue: + return CustomPaint( + painter: HSVWithValueColorPainter(hsvColor)); + case PaletteType.hsl: + case PaletteType.hslWithHue: + return CustomPaint( + painter: HSLWithHueColorPainter(hsvToHsl(hsvColor))); + case PaletteType.hslWithSaturation: + return CustomPaint( + painter: + HSLWithSaturationColorPainter(hsvToHsl(hsvColor))); + case PaletteType.hslWithLightness: + return CustomPaint( + painter: + HSLWithLightnessColorPainter(hsvToHsl(hsvColor))); + case PaletteType.rgbWithRed: + return CustomPaint( + painter: RGBWithRedColorPainter(hsvColor.toColor())); + case PaletteType.rgbWithGreen: + return CustomPaint( + painter: RGBWithGreenColorPainter(hsvColor.toColor())); + case PaletteType.rgbWithBlue: + return CustomPaint( + painter: RGBWithBlueColorPainter(hsvColor.toColor())); + case PaletteType.hueWheel: + return CustomPaint(painter: HUEColorWheelPainter(hsvColor)); + default: + return const CustomPaint(); + } + }, + ), + ); + }, + ); + } +} + +/// Provide Hue Ring with HSV Rectangle of palette widget. +class ColorPickerHueRing extends StatelessWidget { + const ColorPickerHueRing( + this.hsvColor, + this.onColorChanged, { + Key? key, + this.displayThumbColor = true, + this.strokeWidth = 5.0, + }) : super(key: key); + + final HSVColor hsvColor; + final ValueChanged onColorChanged; + final bool displayThumbColor; + final double strokeWidth; + + void _handleGesture( + Offset position, BuildContext context, double height, double width) { + RenderBox? getBox = context.findRenderObject() as RenderBox?; + if (getBox == null) return; + + Offset localOffset = getBox.globalToLocal(position); + double horizontal = localOffset.dx.clamp(0.0, width); + double vertical = localOffset.dy.clamp(0.0, height); + + Offset center = Offset(width / 2, height / 2); + double radio = width <= height ? width / 2 : height / 2; + double dist = + sqrt(pow(horizontal - center.dx, 2) + pow(vertical - center.dy, 2)) / + radio; + double rad = + (atan2(horizontal - center.dx, vertical - center.dy) / pi + 1) / + 2 * + 360; + if (dist > 0.7 && dist < 1.3) + onColorChanged(hsvColor.withHue(((rad + 90) % 360).clamp(0, 360))); + } + + @override + Widget build(BuildContext context) { + return LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + double width = constraints.maxWidth; + double height = constraints.maxHeight; + + return RawGestureDetector( + gestures: { + _AlwaysWinPanGestureRecognizer: + GestureRecognizerFactoryWithHandlers< + _AlwaysWinPanGestureRecognizer>( + () => _AlwaysWinPanGestureRecognizer(), + (_AlwaysWinPanGestureRecognizer instance) { + instance + ..onDown = ((details) => _handleGesture( + details.globalPosition, context, height, width)) + ..onUpdate = ((details) => _handleGesture( + details.globalPosition, context, height, width)); + }, + ), + }, + child: CustomPaint( + painter: HueRingPainter(hsvColor, + displayThumbColor: displayThumbColor, strokeWidth: strokeWidth), + ), + ); + }, + ); + } +} + +class _AlwaysWinPanGestureRecognizer extends PanGestureRecognizer { + @override + void addAllowedPointer(event) { + super.addAllowedPointer(event); + resolve(GestureDisposition.accepted); + } + + @override + String get debugDescription => 'alwaysWin'; +} + +/// Uppercase text formater +class UpperCaseTextFormatter extends TextInputFormatter { + @override + TextEditingValue formatEditUpdate(oldValue, TextEditingValue newValue) => + TextEditingValue( + text: newValue.text.toUpperCase(), selection: newValue.selection); +} diff --git a/lib/src/packages/flutter_colorpicker/src/utils.dart b/lib/src/packages/flutter_colorpicker/src/utils.dart new file mode 100644 index 00000000..bba99353 --- /dev/null +++ b/lib/src/packages/flutter_colorpicker/src/utils.dart @@ -0,0 +1,224 @@ +// ignore_for_file: type=lint + +/// Common function lib + +import 'dart:math'; +import 'package:flutter/painting.dart'; +import 'colors.dart'; + +/// Check if is good condition to use white foreground color by passing +/// the background color, and optional bias. +/// +/// Reference: +/// +/// Old: https://www.w3.org/TR/WCAG20-TECHS/G18.html +/// +/// New: https://github.com/mchome/flutter_statusbarcolor/issues/40 +bool useWhiteForeground(Color backgroundColor, {double bias = 0.0}) { + // Old: + // return 1.05 / (color.computeLuminance() + 0.05) > 4.5; + + // New: + int v = sqrt(pow(backgroundColor.red, 2) * 0.299 + + pow(backgroundColor.green, 2) * 0.587 + + pow(backgroundColor.blue, 2) * 0.114) + .round(); + return v < 130 + bias ? true : false; +} + +/// Convert HSV to HSL +/// +/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_HSL +HSLColor hsvToHsl(HSVColor color) { + double s = 0.0; + double l = 0.0; + l = (2 - color.saturation) * color.value / 2; + if (l != 0) { + if (l == 1) { + s = 0.0; + } else if (l < 0.5) { + s = color.saturation * color.value / (l * 2); + } else { + s = color.saturation * color.value / (2 - l * 2); + } + } + return HSLColor.fromAHSL( + color.alpha, + color.hue, + s.clamp(0.0, 1.0), + l.clamp(0.0, 1.0), + ); +} + +/// Convert HSL to HSV +/// +/// Reference: https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_HSV +HSVColor hslToHsv(HSLColor color) { + double s = 0.0; + double v = 0.0; + + v = color.lightness + + color.saturation * + (color.lightness < 0.5 ? color.lightness : 1 - color.lightness); + if (v != 0) s = 2 - 2 * color.lightness / v; + + return HSVColor.fromAHSV( + color.alpha, + color.hue, + s.clamp(0.0, 1.0), + v.clamp(0.0, 1.0), + ); +} + +/// [RegExp] pattern for validation HEX color [String] inputs, allows only: +/// +/// * exactly 1 to 8 digits in HEX format, +/// * only Latin A-F characters, case insensitive, +/// * and integer numbers 0,1,2,3,4,5,6,7,8,9, +/// * with optional hash (`#`) symbol at the beginning (not calculated in length). +/// +/// ```dart +/// final RegExp hexInputValidator = RegExp(kValidHexPattern); +/// if (hexInputValidator.hasMatch(hex)) print('$hex might be a valid HEX color'); +/// ``` +/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet +const String kValidHexPattern = r'^#?[0-9a-fA-F]{1,8}'; + +/// [RegExp] pattern for validation complete HEX color [String], allows only: +/// +/// * exactly 6 or 8 digits in HEX format, +/// * only Latin A-F characters, case insensitive, +/// * and integer numbers 0,1,2,3,4,5,6,7,8,9, +/// * with optional hash (`#`) symbol at the beginning (not calculated in length). +/// +/// ```dart +/// final RegExp hexCompleteValidator = RegExp(kCompleteValidHexPattern); +/// if (hexCompleteValidator.hasMatch(hex)) print('$hex is valid HEX color'); +/// ``` +/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet +const String kCompleteValidHexPattern = + r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$'; + +/// Try to convert text input or any [String] to valid [Color]. +/// The [String] must be provided in one of those formats: +/// +/// * RGB +/// * #RGB +/// * RRGGBB +/// * #RRGGBB +/// * AARRGGBB +/// * #AARRGGBB +/// +/// Where: A stands for Alpha, R for Red, G for Green, and B for blue color. +/// It will only accept 3/6/8 long HEXs with an optional hash (`#`) at the beginning. +/// Allowed characters are Latin A-F case insensitive and numbers 0-9. +/// Optional [enableAlpha] can be provided (it's `true` by default). If it's set +/// to `false` transparency information (alpha channel) will be removed. +/// ```dart +/// /// // Valid 3 digit HEXs: +/// colorFromHex('abc') == Color(0xffaabbcc) +/// colorFromHex('ABc') == Color(0xffaabbcc) +/// colorFromHex('ABC') == Color(0xffaabbcc) +/// colorFromHex('#Abc') == Color(0xffaabbcc) +/// colorFromHex('#abc') == Color(0xffaabbcc) +/// colorFromHex('#ABC') == Color(0xffaabbcc) +/// // Valid 6 digit HEXs: +/// colorFromHex('aabbcc') == Color(0xffaabbcc) +/// colorFromHex('AABbcc') == Color(0xffaabbcc) +/// colorFromHex('AABBCC') == Color(0xffaabbcc) +/// colorFromHex('#AABbcc') == Color(0xffaabbcc) +/// colorFromHex('#aabbcc') == Color(0xffaabbcc) +/// colorFromHex('#AABBCC') == Color(0xffaabbcc) +/// // Valid 8 digit HEXs: +/// colorFromHex('ffaabbcc') == Color(0xffaabbcc) +/// colorFromHex('ffAABbcc') == Color(0xffaabbcc) +/// colorFromHex('ffAABBCC') == Color(0xffaabbcc) +/// colorFromHex('ffaabbcc', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('ffAABBCC', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('FFaabbcc', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('#ffaabbcc') == Color(0xffaabbcc) +/// colorFromHex('#ffAABbcc') == Color(0xffaabbcc) +/// colorFromHex('#FFAABBCC') == Color(0xffaabbcc) +/// colorFromHex('#ffaabbcc', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('#FFAAbbcc', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('#ffAABBCC', enableAlpha: true) == Color(0xffaabbcc) +/// colorFromHex('#FFaabbcc', enableAlpha: true) == Color(0xffaabbcc) +/// // Invalid HEXs: +/// colorFromHex('bc') == null // length 2 +/// colorFromHex('aabbc') == null // length 5 +/// colorFromHex('#ffaabbccd') == null // length 9 (+#) +/// colorFromHex('aabbcx') == null // x character +/// colorFromHex('#aabbвв') == null // в non-latin character +/// colorFromHex('') == null // empty +/// ``` +/// Reference: https://en.wikipedia.org/wiki/Web_colors#Hex_triplet +Color? colorFromHex(String inputString, {bool enableAlpha = true}) { + // Registers validator for exactly 6 or 8 digits long HEX (with optional #). + final RegExp hexValidator = RegExp(kCompleteValidHexPattern); + // Validating input, if it does not match — it's not proper HEX. + if (!hexValidator.hasMatch(inputString)) return null; + // Remove optional hash if exists and convert HEX to UPPER CASE. + String hexToParse = inputString.replaceFirst('#', '').toUpperCase(); + // It may allow HEXs with transparency information even if alpha is disabled, + if (!enableAlpha && hexToParse.length == 8) { + // but it will replace this info with 100% non-transparent value (FF). + hexToParse = 'FF${hexToParse.substring(2)}'; + } + // HEX may be provided in 3-digits format, let's just duplicate each letter. + if (hexToParse.length == 3) { + hexToParse = hexToParse.split('').expand((i) => [i * 2]).join(); + } + // We will need 8 digits to parse the color, let's add missing digits. + if (hexToParse.length == 6) hexToParse = 'FF$hexToParse'; + // HEX must be valid now, but as a precaution, it will just "try" to parse it. + final intColorValue = int.tryParse(hexToParse, radix: 16); + // If for some reason HEX is not valid — abort the operation, return nothing. + if (intColorValue == null) return null; + // Register output color for the last step. + final color = Color(intColorValue); + // Decide to return color with transparency information or not. + return enableAlpha ? color : color.withAlpha(255); +} + +/// Converts `dart:ui` [Color] to the 6/8 digits HEX [String]. +/// +/// Prefixes a hash (`#`) sign if [includeHashSign] is set to `true`. +/// The result will be provided as UPPER CASE, it can be changed via [toUpperCase] +/// flag set to `false` (default is `true`). Hex can be returned without alpha +/// channel information (transparency), with the [enableAlpha] flag set to `false`. +String colorToHex( + Color color, { + bool includeHashSign = false, + bool enableAlpha = true, + bool toUpperCase = true, +}) { + final String hex = (includeHashSign ? '#' : '') + + (enableAlpha ? _padRadix(color.alpha) : '') + + _padRadix(color.red) + + _padRadix(color.green) + + _padRadix(color.blue); + return toUpperCase ? hex.toUpperCase() : hex; +} + +// Shorthand for padLeft of RadixString, DRY. +String _padRadix(int value) => value.toRadixString(16).padLeft(2, '0'); + +// Extension for String +extension ColorExtension1 on String { + Color? toColor() { + Color? color = colorFromName(this); + if (color != null) return color; + return colorFromHex(this); + } +} + +// Extension from Color +extension ColorExtension2 on Color { + String toHexString( + {bool includeHashSign = false, + bool enableAlpha = true, + bool toUpperCase = true}) => + colorToHex(this, + includeHashSign: false, enableAlpha: true, toUpperCase: true); +} diff --git a/lib/src/widgets/toolbar/buttons/color/color_dialog.dart b/lib/src/widgets/toolbar/buttons/color/color_dialog.dart index e7d65800..fc336932 100644 --- a/lib/src/widgets/toolbar/buttons/color/color_dialog.dart +++ b/lib/src/widgets/toolbar/buttons/color/color_dialog.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; -import 'package:flutter_colorpicker/flutter_colorpicker.dart' - show ColorPicker, MaterialPicker, colorToHex; import '../../../../../translations.dart'; import '../../../../models/documents/style.dart'; +import '../../../../packages/flutter_colorpicker/flutter_colorpicker.dart' + show ColorPicker, MaterialPicker, colorToHex; import 'color_button.dart' show hexToColor; enum _PickerType { diff --git a/pubspec.yaml b/pubspec.yaml index 4eb9954d..ef83fbc4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -51,7 +51,8 @@ dependencies: meta: ^1.10.0 html: ^0.15.4 - flutter_colorpicker: ^1.0.3 + # TODO: temporarily disable from https://pub.dev/packages/flutter_colorpicker and clone it in the `lib/src/packages/flutter_colorpicker` as it's hasn't been published on pub.dev for a while + # flutter_colorpicker: ^1.0.3 # For converting HTML to Quill delta markdown: ^7.2.1 From 171e6f0c41459f6fc6e126c5dfbd67f75e7efb23 Mon Sep 17 00:00:00 2001 From: Ellet Date: Sat, 18 May 2024 14:06:47 +0300 Subject: [PATCH 07/10] ci: an attemp to update the CI to upload the LICENSE file to the release assets --- .github/workflows/publish.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 5afd5600..0ef32fb0 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,15 +9,16 @@ jobs: publish: permissions: id-token: write + contents: write # For uploading to the release assets runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - name: Upload the License to the release assets - uses: actions/upload-artifact@v4 + - name: Upload LICENSE to the release assets + uses: softprops/action-gh-release@v2 + if: startsWith(github.ref, 'refs/tags/') with: - name: LICENSE - path: LICENSE + files: LICENSE - uses: subosito/flutter-action@v2 with: From bcade13db8c8b9f9fc335ff3dc5df86a49531d9f Mon Sep 17 00:00:00 2001 From: Cierra_Runis <2864283875@qq.com> Date: Sun, 19 May 2024 02:17:58 +0800 Subject: [PATCH 08/10] =?UTF-8?q?=F0=9F=94=A7Fix=20`readOnlyMouseCursor`?= =?UTF-8?q?=20losing=20in=20construction=20function=20(#1875)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/src/models/config/editor/editor_configurations.dart | 4 ++++ .../models/config/raw_editor/raw_editor_configurations.dart | 3 ++- lib/src/widgets/editor/editor.dart | 1 + 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/src/models/config/editor/editor_configurations.dart b/lib/src/models/config/editor/editor_configurations.dart index 92c71fbb..74cc66c8 100644 --- a/lib/src/models/config/editor/editor_configurations.dart +++ b/lib/src/models/config/editor/editor_configurations.dart @@ -80,6 +80,7 @@ class QuillEditorConfigurations extends Equatable { this.enableScribble = false, this.onScribbleActivated, this.scribbleAreaInsets, + this.readOnlyMouseCursor = SystemMouseCursors.text, }); final QuillSharedConfigurations sharedConfigurations; @@ -157,6 +158,9 @@ class QuillEditorConfigurations extends Equatable { final bool? showCursor; final bool? paintCursorAboveText; + /// The [readOnlyMouseCursor] is used for Windows, macOS when [readOnly] is [true] + final MouseCursor readOnlyMouseCursor; + /// Whether to enable user interface affordances for changing the /// text selection. /// diff --git a/lib/src/models/config/raw_editor/raw_editor_configurations.dart b/lib/src/models/config/raw_editor/raw_editor_configurations.dart index 51906cfe..b774b9a2 100644 --- a/lib/src/models/config/raw_editor/raw_editor_configurations.dart +++ b/lib/src/models/config/raw_editor/raw_editor_configurations.dart @@ -85,6 +85,7 @@ class QuillRawEditorConfigurations extends Equatable { this.enableScribble = false, this.onScribbleActivated, this.scribbleAreaInsets, + this.readOnlyMouseCursor = SystemMouseCursors.text, }); /// Controls the document being edited. @@ -177,7 +178,7 @@ class QuillRawEditorConfigurations extends Equatable { final CursorStyle cursorStyle; /// The [readOnlyMouseCursor] is used for Windows, macOS when [readOnly] is [true] - final MouseCursor readOnlyMouseCursor = SystemMouseCursors.text; + final MouseCursor readOnlyMouseCursor; /// Configures how the platform keyboard will select an uppercase or /// lowercase keyboard. diff --git a/lib/src/widgets/editor/editor.dart b/lib/src/widgets/editor/editor.dart index a68d15ce..6a1d58e0 100644 --- a/lib/src/widgets/editor/editor.dart +++ b/lib/src/widgets/editor/editor.dart @@ -290,6 +290,7 @@ class QuillEditorState extends State enableScribble: configurations.enableScribble, onScribbleActivated: configurations.onScribbleActivated, scribbleAreaInsets: configurations.scribbleAreaInsets, + readOnlyMouseCursor: configurations.readOnlyMouseCursor, ), ), ), From fcc92da115b07ab8774554f81f29d66daa1076af Mon Sep 17 00:00:00 2001 From: AtlasAutocode <165201146+AtlasAutocode@users.noreply.github.com> Date: Sat, 18 May 2024 12:18:30 -0600 Subject: [PATCH 09/10] Fix block multi-line selection style (#1876) * toggle_style_button : calls to options.afterButtonPressed replaced by call to class function afterButtonPressed to allow default call to base button settings quill_icon_button: L26 build for isSelected updated to call afterButtonPressed = same as if not selected QuillController _updateSelection removed param=source because not used; added new param insertNewline when true set tog to style of preceding char (last entered); updated replaceText to call _updateSelection for NL document collectStyle: Selecting the start of a line, user expects the style to be the visible style of the line including inline styles * color_button calls afterButtonPressed insert at start of line uses style for line * Remove comments * Fix formatting issue * Fix FontFamily and Size button actions * Fix FontFamily and Size button actions * Value setting Stateful toolbar buttons derive from base class * Rename base class as QuillToolbarBaseValueButton * Fixes for before_push script * Removed deprecated functions * Move clipboard actions to QuillController * Fix: collectAllIndividualStylesAndEmbed for result span * Add: Clipboard toolbar buttons * export: Clipboard toolbar buttons * Fix: Dividers not shown in toolbar when multiRowsDisplay. Fix: Toolbar drop buttons clipped when !multiRowsDisplay * Add: test for QuillController clipboard Dart Formatted * Localizations updated * QuillControllerConfigurations and clipboard paste * Fix: CheckList action * Fix: Multiline selection and refactor toolbar buttons * Add tests: Multiline selection --------- Co-authored-by: Douglas Ward --- lib/src/models/documents/nodes/line.dart | 4 +- lib/src/models/rules/insert.dart | 8 +- lib/src/widgets/editor/editor.dart | 1 + lib/src/widgets/quill/quill_controller.dart | 8 +- .../base_button/base_value_button.dart | 81 +++++++++---------- .../base_button/stateless_base_button.dart | 7 +- .../toolbar/buttons/clear_format_button.dart | 2 +- .../toolbar/buttons/color/color_button.dart | 65 ++++----------- .../toolbar/buttons/font_family_button.dart | 13 ++- .../toolbar/buttons/font_size_button.dart | 4 +- .../select_header_style_buttons.dart | 76 ++++++----------- .../select_header_style_dropdown_button.dart | 64 +++++---------- .../toolbar/buttons/history_button.dart | 56 +++++-------- .../toolbar/buttons/indent_button.dart | 68 ++++------------ .../toolbar/buttons/link_style2_button.dart | 19 +++-- .../toolbar/buttons/link_style_button.dart | 65 ++++----------- .../buttons/toggle_check_list_button.dart | 4 +- test/widgets/controller_test.dart | 56 +++++++++++++ 18 files changed, 253 insertions(+), 348 deletions(-) diff --git a/lib/src/models/documents/nodes/line.dart b/lib/src/models/documents/nodes/line.dart index be3eb7b0..df9374a2 100644 --- a/lib/src/models/documents/nodes/line.dart +++ b/lib/src/models/documents/nodes/line.dart @@ -372,7 +372,7 @@ base class Line extends QuillContainer { final data = queryChild(offset, true); var node = data.node as Leaf?; if (node != null) { - result = result.mergeAll(node.style); + result = node.style; var pos = node.length - data.offset; while (!node!.isLast && pos < local) { node = node.next as Leaf; @@ -380,7 +380,6 @@ base class Line extends QuillContainer { pos += node.length; } } - result = result.mergeAll(style); if (parent is Block) { final block = parent as Block; @@ -390,7 +389,6 @@ base class Line extends QuillContainer { final remaining = len - local; if (remaining > 0 && nextLine != null) { final rest = nextLine!.collectStyle(0, remaining); - result = result.mergeAll(rest); handle(rest); } diff --git a/lib/src/models/rules/insert.dart b/lib/src/models/rules/insert.dart index 73fd044e..9b0a3ea7 100644 --- a/lib/src/models/rules/insert.dart +++ b/lib/src/models/rules/insert.dart @@ -564,8 +564,12 @@ class PreserveInlineStylesRule extends InsertRule { if (prev == null || prev.data is! String) return null; if ((prev.data as String).endsWith('\n')) { - if (prev.attributes?.containsKey(Attribute.list.key) == true) { - return null; + if (prev.attributes != null) { + for (final key in prev.attributes!.keys) { + if (Attribute.blockKeys.contains(key)) { + return null; + } + } } prev = itr .next(); // at the start of a line, apply the style for the current line and not the style for the preceding line diff --git a/lib/src/widgets/editor/editor.dart b/lib/src/widgets/editor/editor.dart index 6a1d58e0..a5635967 100644 --- a/lib/src/widgets/editor/editor.dart +++ b/lib/src/widgets/editor/editor.dart @@ -172,6 +172,7 @@ class QuillEditorState extends State @override void initState() { super.initState(); + widget.configurations.controller.editorFocusNode ??= widget.focusNode; _editorKey = configurations.editorKey ?? GlobalKey(); _selectionGestureDetectorBuilder = _QuillEditorSelectionGestureDetectorBuilder( diff --git a/lib/src/widgets/quill/quill_controller.dart b/lib/src/widgets/quill/quill_controller.dart index c8e6a7c3..7e71f201 100644 --- a/lib/src/widgets/quill/quill_controller.dart +++ b/lib/src/widgets/quill/quill_controller.dart @@ -25,14 +25,17 @@ class QuillController extends ChangeNotifier { this.onSelectionCompleted, this.onSelectionChanged, this.readOnly = false, + this.editorFocusNode, }) : _document = document, _selection = selection; factory QuillController.basic( {QuillControllerConfigurations configurations = - const QuillControllerConfigurations()}) { + const QuillControllerConfigurations(), + FocusNode? editorFocusNode}) { return QuillController( configurations: configurations, + editorFocusNode: editorFocusNode, document: Document(), selection: const TextSelection.collapsed(offset: 0), ); @@ -485,6 +488,9 @@ class QuillController extends ChangeNotifier { List get pasteStyleAndEmbed => _pasteStyleAndEmbed; bool readOnly; + /// Used to give focus to the editor following a toolbar action + FocusNode? editorFocusNode; + ImageUrl? _copiedImageUrl; ImageUrl? get copiedImageUrl => _copiedImageUrl; diff --git a/lib/src/widgets/toolbar/base_button/base_value_button.dart b/lib/src/widgets/toolbar/base_button/base_value_button.dart index edc53923..6fc0acb8 100644 --- a/lib/src/widgets/toolbar/base_button/base_value_button.dart +++ b/lib/src/widgets/toolbar/base_button/base_value_button.dart @@ -4,10 +4,10 @@ import '../../../../flutter_quill.dart'; /// The [T] is the options for the button /// The [E] is the extra options for the button -abstract class QuillToolbarBaseValueButton< +abstract class QuillToolbarBaseButton< T extends QuillToolbarBaseButtonOptions, E extends QuillToolbarBaseButtonExtraOptions> extends StatefulWidget { - const QuillToolbarBaseValueButton( + const QuillToolbarBaseButton( {required this.controller, required this.options, super.key}); final T options; @@ -16,16 +16,46 @@ abstract class QuillToolbarBaseValueButton< } /// The [W] is the widget that creates this State -/// The [V] is the type of the currentValue -abstract class QuillToolbarBaseValueButtonState< - W extends QuillToolbarBaseValueButton, +abstract class QuillToolbarCommonButtonState< + W extends QuillToolbarBaseButton, T extends QuillToolbarBaseButtonOptions, - E extends QuillToolbarBaseButtonExtraOptions, - V> extends State { + E extends QuillToolbarBaseButtonExtraOptions> extends State { T get options => widget.options; QuillController get controller => widget.controller; + QuillToolbarBaseButtonOptions? get baseButtonExtraOptions => + context.quillToolbarBaseButtonOptions; + + String get defaultTooltip; + + String get tooltip => + options.tooltip ?? baseButtonExtraOptions?.tooltip ?? defaultTooltip; + + double get iconSize => + options.iconSize ?? baseButtonExtraOptions?.iconSize ?? kDefaultIconSize; + + double get iconButtonFactor => + options.iconButtonFactor ?? + baseButtonExtraOptions?.iconButtonFactor ?? + kDefaultIconButtonFactor; + + QuillIconTheme? get iconTheme => + options.iconTheme ?? baseButtonExtraOptions?.iconTheme; + + VoidCallback? get afterButtonPressed => + options.afterButtonPressed ?? + baseButtonExtraOptions?.afterButtonPressed ?? + () => controller.editorFocusNode?.requestFocus(); +} + +/// The [W] is the widget that creates this State +/// The [V] is the type of the currentValue +abstract class QuillToolbarBaseButtonState< + W extends QuillToolbarBaseButton, + T extends QuillToolbarBaseButtonOptions, + E extends QuillToolbarBaseButtonExtraOptions, + V> extends QuillToolbarCommonButtonState { V? _currentValue; V get currentValue => _currentValue!; set currentValue(V value) => _currentValue = value; @@ -72,46 +102,13 @@ abstract class QuillToolbarBaseValueButtonState< /// Extra listeners allow a subclass to listen to an external event that can affect its currentValue void addExtraListener() {} void removeExtraListener(covariant W oldWidget) {} - - String get defaultTooltip; - - String get tooltip { - return options.tooltip ?? - context.quillToolbarBaseButtonOptions?.tooltip ?? - defaultTooltip; - } - - double get iconSize { - final baseFontSize = baseButtonExtraOptions?.iconSize; - final iconSize = options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = baseButtonExtraOptions?.iconButtonFactor; - final iconButtonFactor = options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - QuillIconTheme? get iconTheme { - return options.iconTheme ?? baseButtonExtraOptions?.iconTheme; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } - - VoidCallback? get afterButtonPressed { - return options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } } -typedef QuillToolbarToggleStyleBaseButton = QuillToolbarBaseValueButton< +typedef QuillToolbarToggleStyleBaseButton = QuillToolbarBaseButton< QuillToolbarToggleStyleButtonOptions, QuillToolbarToggleStyleButtonExtraOptions>; typedef QuillToolbarToggleStyleBaseButtonState< W extends QuillToolbarToggleStyleBaseButton> - = QuillToolbarBaseValueButtonState; diff --git a/lib/src/widgets/toolbar/base_button/stateless_base_button.dart b/lib/src/widgets/toolbar/base_button/stateless_base_button.dart index cd6cc5ed..10039383 100644 --- a/lib/src/widgets/toolbar/base_button/stateless_base_button.dart +++ b/lib/src/widgets/toolbar/base_button/stateless_base_button.dart @@ -33,7 +33,8 @@ abstract class QuillToolbarBaseButton extends StatelessWidget { VoidCallback? afterButtonPressed(BuildContext context) { return options?.afterButtonPressed ?? - baseButtonExtraOptions(context)?.afterButtonPressed; + baseButtonExtraOptions(context)?.afterButtonPressed ?? + () => controller.editorFocusNode?.requestFocus(); } QuillIconTheme? iconTheme(BuildContext context) { @@ -53,11 +54,11 @@ abstract class QuillToolbarBaseButton extends StatelessWidget { String tooltip(BuildContext context) { return options?.tooltip ?? baseButtonExtraOptions(context)?.tooltip ?? - getDefaultIconSize(context); + getDefaultTooltip(context); } abstract final IconData Function(BuildContext context) getDefaultIconData; - abstract final String Function(BuildContext context) getDefaultIconSize; + abstract final String Function(BuildContext context) getDefaultTooltip; Widget buildButton(BuildContext context); Widget? buildCustomChildBuilder( diff --git a/lib/src/widgets/toolbar/buttons/clear_format_button.dart b/lib/src/widgets/toolbar/buttons/clear_format_button.dart index 4a28dcf1..1c7de128 100644 --- a/lib/src/widgets/toolbar/buttons/clear_format_button.dart +++ b/lib/src/widgets/toolbar/buttons/clear_format_button.dart @@ -59,6 +59,6 @@ class QuillToolbarClearFormatButton extends QuillToolbarBaseButton { (context) => Icons.format_clear; @override - String Function(BuildContext context) get getDefaultIconSize => + String Function(BuildContext context) get getDefaultTooltip => (context) => context.loc.clearFormat; } diff --git a/lib/src/widgets/toolbar/buttons/color/color_button.dart b/lib/src/widgets/toolbar/buttons/color/color_button.dart index 77fa424c..5f553160 100644 --- a/lib/src/widgets/toolbar/buttons/color/color_button.dart +++ b/lib/src/widgets/toolbar/buttons/color/color_button.dart @@ -5,39 +5,47 @@ import '../../../../l10n/extensions/localizations.dart'; import '../../../../l10n/widgets/localizations.dart'; import '../../../../models/documents/attribute.dart'; import '../../../../models/documents/style.dart'; -import '../../../../models/themes/quill_icon_theme.dart'; import '../../../../utils/color.dart'; -import '../../../quill/quill_controller.dart'; +import '../../base_button/base_value_button.dart'; import '../../base_toolbar.dart'; import 'color_dialog.dart'; +typedef QuillToolbarColorBaseButton = QuillToolbarBaseButton< + QuillToolbarColorButtonOptions, QuillToolbarColorButtonExtraOptions>; + +typedef QuillToolbarColorBaseButtonState + = QuillToolbarCommonButtonState; + /// Controls color styles. /// /// When pressed, this button displays overlay toolbar with /// buttons for each color. -class QuillToolbarColorButton extends StatefulWidget { +class QuillToolbarColorButton extends QuillToolbarColorBaseButton { const QuillToolbarColorButton({ - required this.controller, + required super.controller, required this.isBackground, - this.options = const QuillToolbarColorButtonOptions(), + super.options = const QuillToolbarColorButtonOptions(), super.key, }); /// Is this background color button or font color final bool isBackground; - final QuillController controller; - final QuillToolbarColorButtonOptions options; @override QuillToolbarColorButtonState createState() => QuillToolbarColorButtonState(); } -class QuillToolbarColorButtonState extends State { +class QuillToolbarColorButtonState extends QuillToolbarColorBaseButtonState { late bool _isToggledColor; late bool _isToggledBackground; late bool _isWhite; late bool _isWhiteBackground; + @override + String get defaultTooltip => + widget.isBackground ? context.loc.backgroundColor : context.loc.fontColor; + Style get _selectionStyle => widget.controller.getSelectionStyle(); void _didChangeEditingValue() { @@ -95,53 +103,12 @@ class QuillToolbarColorButtonState extends State { super.dispose(); } - QuillToolbarColorButtonOptions get options { - return widget.options; - } - - QuillController get controller { - return widget.controller; - } - - double get iconSize { - final baseFontSize = baseButtonExtraOptions?.iconSize; - final iconSize = options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = baseButtonExtraOptions?.iconButtonFactor; - final iconButtonFactor = options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - VoidCallback? get afterButtonPressed { - return options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } - - QuillIconTheme? get iconTheme { - return options.iconTheme ?? baseButtonExtraOptions?.iconTheme; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } - IconData get iconData { return options.iconData ?? baseButtonExtraOptions?.iconData ?? (widget.isBackground ? Icons.format_color_fill : Icons.color_lens); } - String get tooltip { - return options.tooltip ?? - baseButtonExtraOptions?.tooltip ?? - (widget.isBackground - ? context.loc.backgroundColor - : context.loc.fontColor); - } - @override Widget build(BuildContext context) { final iconColor = _isToggledColor && !widget.isBackground && !_isWhite diff --git a/lib/src/widgets/toolbar/buttons/font_family_button.dart b/lib/src/widgets/toolbar/buttons/font_family_button.dart index 10cf8712..3ed3e0d5 100644 --- a/lib/src/widgets/toolbar/buttons/font_family_button.dart +++ b/lib/src/widgets/toolbar/buttons/font_family_button.dart @@ -7,7 +7,7 @@ import '../../../models/documents/attribute.dart'; import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; -class QuillToolbarFontFamilyButton extends QuillToolbarBaseValueButton< +class QuillToolbarFontFamilyButton extends QuillToolbarBaseButton< QuillToolbarFontFamilyButtonOptions, QuillToolbarFontFamilyButtonExtraOptions> { QuillToolbarFontFamilyButton({ @@ -28,12 +28,11 @@ class QuillToolbarFontFamilyButton extends QuillToolbarBaseValueButton< QuillToolbarFontFamilyButtonState(); } -class QuillToolbarFontFamilyButtonState - extends QuillToolbarBaseValueButtonState< - QuillToolbarFontFamilyButton, - QuillToolbarFontFamilyButtonOptions, - QuillToolbarFontFamilyButtonExtraOptions, - String> { +class QuillToolbarFontFamilyButtonState extends QuillToolbarBaseButtonState< + QuillToolbarFontFamilyButton, + QuillToolbarFontFamilyButtonOptions, + QuillToolbarFontFamilyButtonExtraOptions, + String> { @override String get currentStateValue { final attribute = diff --git a/lib/src/widgets/toolbar/buttons/font_size_button.dart b/lib/src/widgets/toolbar/buttons/font_size_button.dart index 71a6bfcc..6a70a531 100644 --- a/lib/src/widgets/toolbar/buttons/font_size_button.dart +++ b/lib/src/widgets/toolbar/buttons/font_size_button.dart @@ -9,7 +9,7 @@ import '../../../utils/font.dart'; import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; -class QuillToolbarFontSizeButton extends QuillToolbarBaseValueButton< +class QuillToolbarFontSizeButton extends QuillToolbarBaseButton< QuillToolbarFontSizeButtonOptions, QuillToolbarFontSizeButtonExtraOptions> { QuillToolbarFontSizeButton({ required super.controller, @@ -28,7 +28,7 @@ class QuillToolbarFontSizeButton extends QuillToolbarBaseValueButton< QuillToolbarFontSizeButtonState(); } -class QuillToolbarFontSizeButtonState extends QuillToolbarBaseValueButtonState< +class QuillToolbarFontSizeButtonState extends QuillToolbarBaseButtonState< QuillToolbarFontSizeButton, QuillToolbarFontSizeButtonOptions, QuillToolbarFontSizeButtonExtraOptions, diff --git a/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart index e04a1f22..53162b7b 100644 --- a/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart +++ b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_buttons.dart @@ -5,29 +5,40 @@ import '../../../../extensions/quill_configurations_ext.dart'; import '../../../../l10n/extensions/localizations.dart'; import '../../../../models/documents/attribute.dart'; import '../../../../models/documents/style.dart'; -import '../../../../models/themes/quill_icon_theme.dart'; -import '../../../quill/quill_controller.dart'; +import '../../base_button/base_value_button.dart'; import '../../base_toolbar.dart'; -class QuillToolbarSelectHeaderStyleButtons extends StatefulWidget { +typedef QuillToolbarSelectHeaderStyleBaseButtons = QuillToolbarBaseButton< + QuillToolbarSelectHeaderStyleButtonsOptions, + QuillToolbarSelectHeaderStyleButtonsExtraOptions>; + +typedef QuillToolbarSelectHeaderStyleBaseButtonsState< + W extends QuillToolbarSelectHeaderStyleBaseButtons> + = QuillToolbarCommonButtonState< + W, + QuillToolbarSelectHeaderStyleButtonsOptions, + QuillToolbarSelectHeaderStyleButtonsExtraOptions>; + +class QuillToolbarSelectHeaderStyleButtons + extends QuillToolbarSelectHeaderStyleBaseButtons { const QuillToolbarSelectHeaderStyleButtons({ - required this.controller, - this.options = const QuillToolbarSelectHeaderStyleButtonsOptions(), + required super.controller, + super.options = const QuillToolbarSelectHeaderStyleButtonsOptions(), super.key, }); - final QuillController controller; - final QuillToolbarSelectHeaderStyleButtonsOptions options; - @override QuillToolbarSelectHeaderStyleButtonsState createState() => QuillToolbarSelectHeaderStyleButtonsState(); } class QuillToolbarSelectHeaderStyleButtonsState - extends State { + extends QuillToolbarSelectHeaderStyleBaseButtonsState { Attribute? _selectedAttribute; + @override + String get defaultTooltip => context.loc.headerStyle; + Style get _selectionStyle => controller.getSelectionStyle(); final _valueToText = { @@ -46,45 +57,6 @@ class QuillToolbarSelectHeaderStyleButtonsState controller.addListener(_didChangeEditingValue); } - QuillToolbarSelectHeaderStyleButtonsOptions get options { - return widget.options; - } - - QuillController get controller { - return widget.controller; - } - - double get iconSize { - final baseFontSize = baseButtonExtraOptions?.iconSize; - final iconSize = options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = baseButtonExtraOptions?.iconButtonFactor; - final iconButtonFactor = options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - VoidCallback? get afterButtonPressed { - return options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } - - QuillIconTheme? get iconTheme { - return options.iconTheme ?? baseButtonExtraOptions?.iconTheme; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } - - String get tooltip { - return options.tooltip ?? - baseButtonExtraOptions?.tooltip ?? - context.loc.headerStyle; - } - Axis get axis { return options.axis ?? context.quillSimpleToolbarConfigurations?.axis ?? @@ -99,7 +71,7 @@ class QuillToolbarSelectHeaderStyleButtonsState afterButtonPressed?.call(); } - List get _attrbuites { + List get _attributes { return options.attributes ?? const [ Attribute.header, @@ -112,7 +84,7 @@ class QuillToolbarSelectHeaderStyleButtonsState @override Widget build(BuildContext context) { assert( - _attrbuites.every( + _attributes.every( (element) => _valueToText.keys.contains(element), ), 'All attributes must be one of them: header, h1, h2 or h3', @@ -126,7 +98,7 @@ class QuillToolbarSelectHeaderStyleButtonsState final childBuilder = options.childBuilder ?? baseButtonExtraOptions?.childBuilder; - final children = _attrbuites.map((attribute) { + final children = _attributes.map((attribute) { if (childBuilder != null) { return childBuilder( options, @@ -149,7 +121,7 @@ class QuillToolbarSelectHeaderStyleButtonsState icon: Text( _valueToText[attribute] ?? (throw ArgumentError.notNull( - 'attrbuite', + 'attribute', )), style: style.copyWith( color: isSelected diff --git a/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart index 11990200..7a84ba5d 100644 --- a/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart +++ b/lib/src/widgets/toolbar/buttons/hearder_style/select_header_style_dropdown_button.dart @@ -3,27 +3,38 @@ import 'package:flutter/material.dart'; import '../../../../../translations.dart'; import '../../../../extensions/quill_configurations_ext.dart'; import '../../../../models/documents/attribute.dart'; -import '../../../../models/themes/quill_icon_theme.dart'; -import '../../../quill/quill_controller.dart'; +import '../../base_button/base_value_button.dart'; import '../../base_toolbar.dart'; -class QuillToolbarSelectHeaderStyleDropdownButton extends StatefulWidget { +typedef QuillToolbarSelectHeaderStyleDropdownBaseButton + = QuillToolbarBaseButton; + +typedef QuillToolbarSelectHeaderStyleDropdownBaseButtonsState< + W extends QuillToolbarSelectHeaderStyleDropdownButton> + = QuillToolbarCommonButtonState< + W, + QuillToolbarSelectHeaderStyleDropdownButtonOptions, + QuillToolbarSelectHeaderStyleDropdownButtonExtraOptions>; + +class QuillToolbarSelectHeaderStyleDropdownButton + extends QuillToolbarSelectHeaderStyleDropdownBaseButton { const QuillToolbarSelectHeaderStyleDropdownButton({ - required this.controller, - this.options = const QuillToolbarSelectHeaderStyleDropdownButtonOptions(), + required super.controller, + super.options = const QuillToolbarSelectHeaderStyleDropdownButtonOptions(), super.key, }); - final QuillController controller; - final QuillToolbarSelectHeaderStyleDropdownButtonOptions options; - @override - State createState() => + QuillToolbarSelectHeaderStyleDropdownBaseButtonsState createState() => _QuillToolbarSelectHeaderStyleDropdownButtonState(); } class _QuillToolbarSelectHeaderStyleDropdownButtonState - extends State { + extends QuillToolbarSelectHeaderStyleDropdownBaseButtonsState { + @override + String get defaultTooltip => context.loc.headerStyle; + Attribute _selectedItem = Attribute.header; final _menuController = MenuController(); @@ -89,24 +100,6 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState return label; } - double get iconSize { - final baseFontSize = context.quillToolbarBaseButtonOptions?.iconSize; - final iconSize = widget.options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = - context.quillToolbarBaseButtonOptions?.iconButtonFactor; - final iconButtonFactor = widget.options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - QuillIconTheme? get iconTheme { - return widget.options.iconTheme ?? - context.quillToolbarBaseButtonOptions?.iconTheme; - } - List> get headerAttributes { return widget.options.attributes ?? [ @@ -117,21 +110,6 @@ class _QuillToolbarSelectHeaderStyleDropdownButtonState ]; } - String get tooltip { - return widget.options.tooltip ?? - context.quillToolbarBaseButtonOptions?.tooltip ?? - context.loc.headerStyle; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } - - VoidCallback? get afterButtonPressed { - return widget.options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } - void _onPressed(Attribute e) { setState(() => _selectedItem = e); widget.controller.formatSelection(_selectedItem); diff --git a/lib/src/widgets/toolbar/buttons/history_button.dart b/lib/src/widgets/toolbar/buttons/history_button.dart index 9751a696..09284a84 100644 --- a/lib/src/widgets/toolbar/buttons/history_button.dart +++ b/lib/src/widgets/toolbar/buttons/history_button.dart @@ -1,15 +1,21 @@ import 'package:flutter/material.dart'; -import '../../../extensions/quill_configurations_ext.dart'; import '../../../l10n/extensions/localizations.dart'; -import '../../quill/quill_controller.dart'; +import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; -class QuillToolbarHistoryButton extends StatefulWidget { +typedef QuillToolbarHistoryBaseButton = QuillToolbarBaseButton< + QuillToolbarHistoryButtonOptions, QuillToolbarHistoryButtonExtraOptions>; + +typedef QuillToolbarHistoryBaseButtonState + = QuillToolbarCommonButtonState; + +class QuillToolbarHistoryButton extends QuillToolbarHistoryBaseButton { const QuillToolbarHistoryButton({ - required this.controller, + required super.controller, required this.isUndo, - this.options = const QuillToolbarHistoryButtonOptions(), + super.options = const QuillToolbarHistoryButtonOptions(), super.key, }); @@ -17,25 +23,18 @@ class QuillToolbarHistoryButton extends StatefulWidget { /// otherwise it will be redo final bool isUndo; - final QuillToolbarHistoryButtonOptions options; - final QuillController controller; - @override QuillToolbarHistoryButtonState createState() => QuillToolbarHistoryButtonState(); } -class QuillToolbarHistoryButtonState extends State { - late ThemeData theme; +class QuillToolbarHistoryButtonState + extends QuillToolbarHistoryBaseButtonState { var _canPressed = false; - QuillToolbarHistoryButtonOptions get options { - return widget.options; - } - - QuillController get controller { - return widget.controller; - } + @override + String get defaultTooltip => + widget.isUndo ? context.loc.undo : context.loc.redo; @override void initState() { @@ -54,25 +53,11 @@ class QuillToolbarHistoryButtonState extends State { @override Widget build(BuildContext context) { - final baseButtonConfigurations = context.quillToolbarBaseButtonOptions; - final tooltip = options.tooltip ?? - baseButtonConfigurations?.tooltip ?? - (widget.isUndo ? context.loc.undo : context.loc.redo); final iconData = options.iconData ?? - baseButtonConfigurations?.iconData ?? + baseButtonExtraOptions?.iconData ?? (widget.isUndo ? Icons.undo_outlined : Icons.redo_outlined); final childBuilder = - options.childBuilder ?? baseButtonConfigurations?.childBuilder; - final iconSize = options.iconSize ?? - baseButtonConfigurations?.iconSize ?? - kDefaultIconSize; - final iconButtonFactor = options.iconButtonFactor ?? - baseButtonConfigurations?.iconButtonFactor ?? - kDefaultIconButtonFactor; - final iconTheme = options.iconTheme ?? baseButtonConfigurations?.iconTheme; - - final afterButtonPressed = options.afterButtonPressed ?? - baseButtonConfigurations?.afterButtonPressed; + options.childBuilder ?? baseButtonExtraOptions?.childBuilder; if (childBuilder != null) { return childBuilder( @@ -89,7 +74,6 @@ class QuillToolbarHistoryButtonState extends State { ); } - theme = Theme.of(context); return QuillToolbarIconButton( tooltip: tooltip, icon: Icon( @@ -122,13 +106,13 @@ class QuillToolbarHistoryButtonState extends State { if (controller.hasUndo) { controller.undo(); } - // _updateCanPressed(); // We are already listeneting for the changes + // _updateCanPressed(); // We are already listening for the changes return; } if (controller.hasRedo) { controller.redo(); - // _updateCanPressed(); // We are already listeneting for the changes + // _updateCanPressed(); // We are already listening for the changes } } } diff --git a/lib/src/widgets/toolbar/buttons/indent_button.dart b/lib/src/widgets/toolbar/buttons/indent_button.dart index 2b2453a4..a750e7fc 100644 --- a/lib/src/widgets/toolbar/buttons/indent_button.dart +++ b/lib/src/widgets/toolbar/buttons/indent_button.dart @@ -1,63 +1,37 @@ import 'package:flutter/material.dart'; -import '../../../extensions/quill_configurations_ext.dart'; import '../../../l10n/extensions/localizations.dart'; import '../../../models/config/toolbar/simple_toolbar_configurations.dart'; -import '../../../models/themes/quill_icon_theme.dart'; -import '../../quill/quill_controller.dart'; -import '../base_toolbar.dart' - show QuillToolbarBaseButtonOptions, QuillToolbarIconButton; +import '../base_button/base_value_button.dart'; +import '../base_toolbar.dart' show QuillToolbarIconButton; -class QuillToolbarIndentButton extends StatefulWidget { +typedef QuillToolbarIndentBaseButton = QuillToolbarBaseButton< + QuillToolbarIndentButtonOptions, QuillToolbarIndentButtonExtraOptions>; + +typedef QuillToolbarIndentBaseButtonState + = QuillToolbarCommonButtonState; + +class QuillToolbarIndentButton extends QuillToolbarIndentBaseButton { const QuillToolbarIndentButton({ - required this.controller, + required super.controller, required this.isIncrease, - this.options = const QuillToolbarIndentButtonOptions(), + super.options = const QuillToolbarIndentButtonOptions(), super.key, }); - final QuillController controller; final bool isIncrease; - final QuillToolbarIndentButtonOptions options; @override QuillToolbarIndentButtonState createState() => QuillToolbarIndentButtonState(); } -class QuillToolbarIndentButtonState extends State { - QuillToolbarIndentButtonOptions get options { - return widget.options; - } - - QuillController get controller { - return widget.controller; - } - - double get iconSize { - final baseFontSize = baseButtonExtraOptions?.iconSize; - final iconSize = options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = baseButtonExtraOptions?.iconButtonFactor; - final iconButtonFactor = options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - VoidCallback? get afterButtonPressed { - return options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } - - QuillIconTheme? get iconTheme { - return options.iconTheme ?? baseButtonExtraOptions?.iconTheme; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } +class QuillToolbarIndentButtonState extends QuillToolbarIndentBaseButtonState { + @override + String get defaultTooltip => widget.isIncrease + ? context.loc.increaseIndent + : context.loc.decreaseIndent; IconData get iconData { return options.iconData ?? @@ -67,14 +41,6 @@ class QuillToolbarIndentButtonState extends State { : Icons.format_indent_decrease); } - String get tooltip { - return options.tooltip ?? - baseButtonExtraOptions?.tooltip ?? - (widget.isIncrease - ? context.loc.increaseIndent - : context.loc.decreaseIndent); - } - void _sharedOnPressed() { widget.controller.indentSelection(widget.isIncrease); } diff --git a/lib/src/widgets/toolbar/buttons/link_style2_button.dart b/lib/src/widgets/toolbar/buttons/link_style2_button.dart index ff54f117..b6178cdd 100644 --- a/lib/src/widgets/toolbar/buttons/link_style2_button.dart +++ b/lib/src/widgets/toolbar/buttons/link_style2_button.dart @@ -12,15 +12,25 @@ import '../../../models/themes/quill_dialog_theme.dart'; import '../../../models/themes/quill_icon_theme.dart'; import '../../others/link.dart'; import '../../quill/quill_controller.dart'; +import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; +typedef QuillToolbarLinkStyleBaseButton2 = QuillToolbarBaseButton< + QuillToolbarLinkStyleButton2Options, + QuillToolbarLinkStyleButton2ExtraOptions>; + +typedef QuillToolbarLinkStyleBaseButton2State< + W extends QuillToolbarLinkStyleBaseButton2> + = QuillToolbarCommonButtonState; + /// Alternative version of [QuillToolbarLinkStyleButton]. This widget has more /// customization /// and uses dialog similar to one which is used on [http://quilljs.com]. -class QuillToolbarLinkStyleButton2 extends StatefulWidget { +class QuillToolbarLinkStyleButton2 extends QuillToolbarLinkStyleBaseButton2 { QuillToolbarLinkStyleButton2({ - required this.controller, - this.options = const QuillToolbarLinkStyleButton2Options(), + required super.controller, + super.options = const QuillToolbarLinkStyleButton2Options(), super.key, }) : assert(options.addLinkLabel == null || (options.addLinkLabel?.isNotEmpty ?? true)), @@ -30,9 +40,6 @@ class QuillToolbarLinkStyleButton2 extends StatefulWidget { assert(options.validationMessage == null || (options.validationMessage?.isNotEmpty ?? true)); - final QuillController controller; - final QuillToolbarLinkStyleButton2Options options; - @override State createState() => _QuillToolbarLinkStyleButton2State(); diff --git a/lib/src/widgets/toolbar/buttons/link_style_button.dart b/lib/src/widgets/toolbar/buttons/link_style_button.dart index dfa22817..cb8f24b8 100644 --- a/lib/src/widgets/toolbar/buttons/link_style_button.dart +++ b/lib/src/widgets/toolbar/buttons/link_style_button.dart @@ -7,28 +7,36 @@ import '../../../models/documents/attribute.dart'; import '../../../models/rules/insert.dart'; import '../../../models/structs/link_dialog_action.dart'; import '../../../models/themes/quill_dialog_theme.dart'; -import '../../../models/themes/quill_icon_theme.dart'; import '../../others/link.dart'; -import '../../quill/quill_controller.dart'; +import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; -class QuillToolbarLinkStyleButton extends StatefulWidget { +typedef QuillToolbarLinkStyleBaseButton = QuillToolbarBaseButton< + QuillToolbarLinkStyleButtonOptions, + QuillToolbarLinkStyleButtonExtraOptions>; + +typedef QuillToolbarLinkStyleBaseButtonState< + W extends QuillToolbarLinkStyleBaseButton> + = QuillToolbarCommonButtonState; + +class QuillToolbarLinkStyleButton extends QuillToolbarLinkStyleBaseButton { const QuillToolbarLinkStyleButton({ - required this.controller, - this.options = const QuillToolbarLinkStyleButtonOptions(), + required super.controller, + super.options = const QuillToolbarLinkStyleButtonOptions(), super.key, }); - final QuillController controller; - final QuillToolbarLinkStyleButtonOptions options; - @override QuillToolbarLinkStyleButtonState createState() => QuillToolbarLinkStyleButtonState(); } class QuillToolbarLinkStyleButtonState - extends State { + extends QuillToolbarLinkStyleBaseButtonState { + @override + String get defaultTooltip => context.loc.insertURL; + void _didChangeSelection() { setState(() {}); } @@ -54,45 +62,6 @@ class QuillToolbarLinkStyleButtonState controller.removeListener(_didChangeSelection); } - QuillController get controller { - return widget.controller; - } - - QuillToolbarLinkStyleButtonOptions get options { - return widget.options; - } - - double get iconSize { - final baseFontSize = baseButtonExtraOptions?.iconSize; - final iconSize = options.iconSize; - return iconSize ?? baseFontSize ?? kDefaultIconSize; - } - - double get iconButtonFactor { - final baseIconFactor = baseButtonExtraOptions?.iconButtonFactor; - final iconButtonFactor = options.iconButtonFactor; - return iconButtonFactor ?? baseIconFactor ?? kDefaultIconButtonFactor; - } - - VoidCallback? get afterButtonPressed { - return options.afterButtonPressed ?? - baseButtonExtraOptions?.afterButtonPressed; - } - - QuillIconTheme? get iconTheme { - return options.iconTheme ?? baseButtonExtraOptions?.iconTheme; - } - - QuillToolbarBaseButtonOptions? get baseButtonExtraOptions { - return context.quillToolbarBaseButtonOptions; - } - - String get tooltip { - return options.tooltip ?? - baseButtonExtraOptions?.tooltip ?? - context.loc.insertURL; - } - IconData get iconData { return options.iconData ?? baseButtonExtraOptions?.iconData ?? Icons.link; } diff --git a/lib/src/widgets/toolbar/buttons/toggle_check_list_button.dart b/lib/src/widgets/toolbar/buttons/toggle_check_list_button.dart index 00a46db3..2bf6825b 100644 --- a/lib/src/widgets/toolbar/buttons/toggle_check_list_button.dart +++ b/lib/src/widgets/toolbar/buttons/toggle_check_list_button.dart @@ -7,7 +7,7 @@ import '../../../utils/widgets.dart'; import '../base_button/base_value_button.dart'; import '../base_toolbar.dart'; -class QuillToolbarToggleCheckListButton extends QuillToolbarBaseValueButton< +class QuillToolbarToggleCheckListButton extends QuillToolbarBaseButton< QuillToolbarToggleCheckListButtonOptions, QuillToolbarToggleCheckListButtonExtraOptions> { const QuillToolbarToggleCheckListButton({ @@ -22,7 +22,7 @@ class QuillToolbarToggleCheckListButton extends QuillToolbarBaseValueButton< } class QuillToolbarToggleCheckListButtonState - extends QuillToolbarBaseValueButtonState< + extends QuillToolbarBaseButtonState< QuillToolbarToggleCheckListButton, QuillToolbarToggleCheckListButtonOptions, QuillToolbarToggleCheckListButtonExtraOptions, diff --git a/test/widgets/controller_test.dart b/test/widgets/controller_test.dart index 10133064..d162a543 100644 --- a/test/widgets/controller_test.dart +++ b/test/widgets/controller_test.dart @@ -355,5 +355,61 @@ void main() { expect(controller.document.length, 6, reason: 'Cut not permitted on readOnly document'); }); + + test('blockSelectionStyles', () { + Style select(int start, int end) { + controller.updateSelection( + TextSelection(baseOffset: start, extentOffset: end), + ChangeSource.local); + return controller.getSelectionStyle(); + } + + Attribute fromKey(String key) => switch (key) { + 'header' => Attribute.h1, + 'list' => Attribute.ol, + 'align' => Attribute.centerAlignment, + 'code-block' => Attribute.codeBlock, + 'blockquote' => Attribute.blockQuote, + 'indent' => Attribute.indentL2, + 'direction' => Attribute.rtl, + String() => throw UnimplementedError(key) + }; + + for (final blockKey in Attribute.blockKeys) { + final blockAttribute = fromKey(blockKey); + controller + ..clear() + ..replaceText(0, 0, 'line 1\nLine 2\nLine 3', null) + ..formatText(0, 0, blockAttribute) // first 2 lines + ..formatText( + 4, 6, Attribute.bold) // spans end of line 1 and start of line 2 + ..formatText(7, 0, blockAttribute); + + expect(select(2, 5), const Style().put(blockAttribute), + reason: 'line 1 block, plain and bold'); + expect( + select(5, 6), const Style().put(Attribute.bold).put(blockAttribute), + reason: 'line 1 block, bold'); + expect( + select(4, 8), const Style().put(Attribute.bold).put(blockAttribute), + reason: 'spans line1 and 2, selection is all bold'); + expect(select(4, 11), const Style().put(blockAttribute), + reason: 'selection expands into non-bold text'); + expect(select(2, 11), const Style().put(blockAttribute), + reason: + 'selection starts in non-bold text extends into plain on next line'); + expect(select(2, 8), const Style().put(blockAttribute), + reason: + 'selection starts in non-bold text, extends into bold on next line'); + + expect( + select(7, 8), const Style().put(Attribute.bold).put(blockAttribute), + reason: 'line 2 block, bold'); + expect(select(7, 11), const Style().put(blockAttribute), + reason: 'line 2 block, selection extends into plain text'); + expect(select(4, 16), const Style(), + reason: 'line 1 extends into line3 which is not block'); + } + }); }); } From c4e47fd18aca234fb33632357d2d8a1bfdbd14ee Mon Sep 17 00:00:00 2001 From: Cheryl Date: Sat, 18 May 2024 11:21:45 -0700 Subject: [PATCH 10/10] Upgrade to 9.3.13 --- CHANGELOG.md | 4 ++++ dart_quill_delta/CHANGELOG.md | 4 ++++ dart_quill_delta/pubspec.yaml | 2 +- flutter_quill_extensions/CHANGELOG.md | 4 ++++ flutter_quill_extensions/pubspec.yaml | 4 ++-- flutter_quill_test/CHANGELOG.md | 4 ++++ flutter_quill_test/pubspec.yaml | 2 +- pubspec.yaml | 2 +- quill_html_converter/CHANGELOG.md | 4 ++++ quill_html_converter/pubspec.yaml | 2 +- quill_pdf_converter/CHANGELOG.md | 4 ++++ quill_pdf_converter/pubspec.yaml | 2 +- version.dart | 2 +- 13 files changed, 32 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/dart_quill_delta/CHANGELOG.md b/dart_quill_delta/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/dart_quill_delta/CHANGELOG.md +++ b/dart_quill_delta/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/dart_quill_delta/pubspec.yaml b/dart_quill_delta/pubspec.yaml index 5dc7388c..c3e6b1c9 100644 --- a/dart_quill_delta/pubspec.yaml +++ b/dart_quill_delta/pubspec.yaml @@ -1,6 +1,6 @@ name: dart_quill_delta description: A port of quill-js-delta from typescript to dart -version: 9.3.12 +version: 9.3.13 homepage: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ repository: https://github.com/singerdmx/flutter-quill/tree/master/dart_quill_delta/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index b5319b35..a4fe87e0 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_extensions description: Embed extensions for flutter_quill including image, video, formula and etc. -version: 9.3.12 +version: 9.3.13 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_extensions/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ @@ -35,7 +35,7 @@ dependencies: universal_html: ^2.2.4 cross_file: ^0.3.3+6 - flutter_quill: ^9.3.4 + flutter_quill: ^9.3.12 photo_view: ^0.14.0 # Plugins diff --git a/flutter_quill_test/CHANGELOG.md b/flutter_quill_test/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/flutter_quill_test/CHANGELOG.md +++ b/flutter_quill_test/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/flutter_quill_test/pubspec.yaml b/flutter_quill_test/pubspec.yaml index 68a4c1ed..2d24aa8c 100644 --- a/flutter_quill_test/pubspec.yaml +++ b/flutter_quill_test/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill_test description: Test utilities for flutter_quill which includes methods to simplify interacting with the editor in test cases. -version: 9.3.12 +version: 9.3.13 homepage: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ repository: https://github.com/singerdmx/flutter-quill/tree/master/flutter_quill_test/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/pubspec.yaml b/pubspec.yaml index ef83fbc4..6197a663 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_quill description: A rich text editor built for the modern Android, iOS, web and desktop platforms. It is the WYSIWYG editor and a Quill component for Flutter. -version: 9.3.12 +version: 9.3.13 homepage: https://1o24bbs.com/c/bulletjournal/108/ repository: https://github.com/singerdmx/flutter-quill/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_html_converter/CHANGELOG.md b/quill_html_converter/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/quill_html_converter/CHANGELOG.md +++ b/quill_html_converter/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/quill_html_converter/pubspec.yaml b/quill_html_converter/pubspec.yaml index 83fd79ea..1b334c25 100644 --- a/quill_html_converter/pubspec.yaml +++ b/quill_html_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_html_converter description: A extension for flutter_quill package to add support for dealing with conversion to/from html -version: 9.3.12 +version: 9.3.13 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_html_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/quill_pdf_converter/CHANGELOG.md b/quill_pdf_converter/CHANGELOG.md index f7d8ac0d..2da09dcd 100644 --- a/quill_pdf_converter/CHANGELOG.md +++ b/quill_pdf_converter/CHANGELOG.md @@ -2,6 +2,10 @@ All notable changes to this project will be documented in this file. +## 9.3.13 +* fix: `readOnlyMouseCursor` losing in construction function +* Fix block multi-line selection style + ## 9.3.12 * Add `readOnlyMouseCursor` to config mouse cursor type diff --git a/quill_pdf_converter/pubspec.yaml b/quill_pdf_converter/pubspec.yaml index b0ee64a9..ddf9392f 100644 --- a/quill_pdf_converter/pubspec.yaml +++ b/quill_pdf_converter/pubspec.yaml @@ -1,6 +1,6 @@ name: quill_pdf_converter description: A extension for flutter_quill package to add support for dealing with conversion to pdf -version: 9.3.12 +version: 9.3.13 homepage: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ repository: https://github.com/singerdmx/flutter-quill/tree/master/quill_pdf_converter/ issue_tracker: https://github.com/singerdmx/flutter-quill/issues/ diff --git a/version.dart b/version.dart index f32c85eb..97dc4203 100644 --- a/version.dart +++ b/version.dart @@ -1 +1 @@ -const version = '9.3.12'; +const version = '9.3.13';