diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs new file mode 100644 index 000000000..e4a90aab6 --- /dev/null +++ b/.git-blame-ignore-revs @@ -0,0 +1,9 @@ +# Revisions for `git blame` to skip (large, mechanical changes with no logic impact). +# +# Enable locally once with: +# git config blame.ignoreRevsFile .git-blame-ignore-revs +# +# GitHub honors this file automatically. List full 40-char commit SHAs only. + +# style: apply Dart 3.12 format (tall style) — repo-wide dart_format, no logic changes +10e0814088ae3b12f0cca4673f885e2828eaaa7a diff --git a/.github/workflows/checks.yml b/.github/workflows/checks.yml index cf86d8252..596ac6210 100644 --- a/.github/workflows/checks.yml +++ b/.github/workflows/checks.yml @@ -16,6 +16,10 @@ jobs: - name: Set up Flutter uses: subosito/flutter-action@v2 with: + # Pinned so a future Flutter/Dart release can't change formatter output + # and turn the format check below red on unrelated PRs. Bump deliberately + # alongside a repo-wide reformat (see .git-blame-ignore-revs). + flutter-version: 3.44.1 channel: stable cache: true @@ -34,9 +38,8 @@ jobs: - name: Perform Flutter Analysis run: flutter analyze - # TODO: Restore Dart formatting check, disabled due to an issue https://github.com/singerdmx/flutter-quill/actions/runs/13355109619/job/37296760819. - # - name: Check Dart code formatting - # run: dart format --set-exit-if-changed . + - name: Check Dart code formatting + run: dart format --set-exit-if-changed . - name: Preview Dart proposed changes run: dart fix --dry-run diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml index 601c3fbaa..41787d628 100644 --- a/.github/workflows/pr-checks.yml +++ b/.github/workflows/pr-checks.yml @@ -34,6 +34,7 @@ jobs: - if: ${{ !cancelled() }} name: Run Flutter Analyze Commenter + continue-on-error: true uses: yorifuji/flutter-analyze-commenter@v1 with: analyze-log: flutter_analyze.log diff --git a/CHANGELOG.md b/CHANGELOG.md index 028f986b9..81cfeb407 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,22 +10,64 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] -## [11.5.0] - 2026-10-07 +## [11.5.2] - 2026-10-07 ### Added - `QuillFocusNode`, which can disable the keyboard while the editor keeps focus. +- Added `QuillEditorConfig.showCodeBlockLineNumbers` (defaults to `true`). When set to `false`, code-block line numbers are hidden and the left gutter shrinks to match the code block's right indent, giving the block symmetric horizontal padding instead of a flush-left edge. `customLeadingBlockBuilder` remains available for finer control and still takes precedence when both are set. ### Fixed - Memory leaks in the editor, selection overlay, and toolbar. Notifiers, listeners, and overlay entries the editor creates are now disposed. - Crash when a focus retry runs after the editor is disposed. -- Implement `onFocusReceived()` so the editor builds on Flutter 3.44.0. -- Stop exporting the deprecated internal `linkPrefixes` constant. +- Fixed an issue where bullet points became visually detached from the text body when toggling text direction formatting (RTL) by locking the list leading block to the editor's base text direction. +- Fixed a brief toolbar flicker when tapping a checkbox: the header, inline, and color buttons momentarily reflected the tapped line's style and the checklist button briefly toggled before the selection was restored. The checkbox tap's gesture-driven caret move is now ignored and the checkbox is formatted silently. +- Fixed typed text being inserted at the previous caret position on Android after moving the caret with a tap/mouse by keeping the platform IME's editing state in sync with the selection even when the keyboard is hidden. +- Added `transformLink` callback to `QuillEditorConfig` (and `RawEditorConfig`) to allow customizing how a link string is transformed before it is launched on tap. When `null`, the previous default behavior (prepending `https://` to scheme-less links) is preserved [#2727](https://github.com/singerdmx/flutter-quill/pull/2727). +- Whole-word search now matches non-ASCII text (Cyrillic, CJK, accented Latin). +- Fixed dragging the right selection handle horizontally extends the selection into the line below instead of staying on the current line. +- Code block line numbers were not visible for non-indented code blocks because the leading-gutter width was reserved at zero pixels in `TextBlockUtils.defaultIndentWidthBuilder`. The gutter is now sized correctly so line numbers are visible by default. Nested indent levels continue to rotate the numbering style through Arabic, lowercase letters, and lowercase Roman numerals, matching ordered-list behavior. +- Fixed the selection (Cut/Copy/Paste) context menu not appearing when the selection extends beyond the visible viewport — e.g. selecting a range taller than the viewport, scrolling a selection out of view, or **Select All** on a long document. The context menu anchors are now clamped into the editor's visible region so it stays on-screen, matching the behavior of a multiline `TextField`. +- Reveal the caret when the bottom view inset (on-screen keyboard) changes, so it is no longer hidden behind the keyboard when it appears or changes height mid-edit (e.g. on app resume, IME/emoji switch, rotation, split-screen or foldable resize). `QuillRawEditorState` now overrides `didChangeMetrics` and re-reveals the caret once the inset settles. + +### Deprecated + +- Deprecated `QuillController.toolbarButtonToggler`. It was an internal-only workaround for syncing a few toolbar block buttons during checkbox taps and no longer has any effect now that the flicker is fixed at its source. Toolbar button state derives from `getSelectionStyle()`. It will be removed in future versions. + +### Removed + +- Removed the already-`@Deprecated` and `@internal` `linkPrefixes` constant from the public API surface (it is hidden from the `flutter_quill.dart` export). Use `LinkValidator.linkPrefixes` instead. ### Changed - Depend on the T-Pro `quill_native_bridge` fork with `xclip` removed. +- Search dialog buttons now adopt the toolbar's `QuillIconTheme`; bar height and spacing tightened. +- Tightened the code-block leading gutter: its reserved width is now slightly narrower than the ordered-list gutter (by `fontSize / 4`), pulling the right-aligned line number closer to the left edge of the block. +- Reduced the right-side padding inside the code-block leading container from `fontSize` to `fontSize / 2`, shrinking the gap between the line number and the code text to match ordered/unordered lists. +- Reduced the left content padding inside the code-block decoration from `16` to `4`, so the line number sits closer to the left edge of the grey code-block box. +- Made the code-block content padding symmetric (`EdgeInsets.symmetric(horizontal: 4, vertical: 16)`, previously `16` on the right), and added a `fontSize / 2` right indent to code blocks in `TextBlockUtils.defaultIndentWidthBuilder` so the code text no longer runs to the right edge of the box. The right indent is part of the default `indentWidthBuilder`, so a custom `indentWidthBuilder` can override it. + +## [11.5.1] - 2026-05-20 + +### Added + +- Added localization support for `mn` (Mongolian, Mongolia) + +### Changed + +- Updated minimum supported SDK version to Flutter 3.44/Dart 3.12. +- Implemented the new [TextInputClient.onFocusReceived](https://github.com/flutter/flutter/blob/stable/packages/flutter/lib/src/services/text_input.dart#L1395-L1401) method required by Flutter SDK 3.44+ (`returns false`). + +## [11.5.0] - 2025-10-18 + +### Fixed + +- Fixed `View.of(context)` calls throwing when used with the `screenshot` package [#2662](https://github.com/singerdmx/flutter-quill/pull/2662). + +### Added + +- Add missing Brazilian Portuguese translations ## [11.4.2] - 2025-07-22 @@ -190,8 +232,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Apple-specific font dependency for subscript and superscript functionality from the example. - **BREAKING**: The [`super_clipboard`](https://pub.dev/packages/super_clipboard) plugin, To restore legacy behavior for `super_clipboard`, use [`flutter_quill_extensions`](https://pub.dev/packages/flutter_quill_extensions) package and `FlutterQuillExtensions.useSuperClipboardPlugin()`. -[unreleased]: https://github.com/singerdmx/flutter-quill/compare/v11.5.0...HEAD -[11.5.0]: https://github.com/singerdmx/flutter-quill/compare/v11.4.2...v11.5.0 +[unreleased]: https://github.com/singerdmx/flutter-quill/compare/v11.5.2...HEAD +[11.5.2]: https://github.com/singerdmx/flutter-quill/compare/v11.5.1...v11.5.2 +[11.5.1]: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v11.5.1 +[11.5.0]: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v11.5.0 [11.4.2]: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v11.4.2 [11.4.1]: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v11.4.1 [11.4.0]: https://github.com/singerdmx/flutter-quill/compare/v10.0.0...v11.4.0 diff --git a/doc/translation.md b/doc/translation.md index 416ea2f93..43c429519 100644 --- a/doc/translation.md +++ b/doc/translation.md @@ -5,12 +5,12 @@ your `WidgetsApp` for example `MaterialApp` which usually follows the system loc ## 🌐 Supported Locales -Currently, translations are available for these 48 locales: +Currently, translations are available for these 49 locales: * `ar`, `bg`, `bn`, `ca`, `cs`, `da`, `de` * `en`, `en_US`, `es`, `fa`, `fr`, `he` * `hi`, `id`, `it`, `ja`, `ko`, `km`, `ku` -* `ms`, `ne`, `nl`, `no`, `pl`, `pt` +* `mn`, `ms`, `ne`, `nl`, `no`, `pl`, `pt` * `pt_BR`, `ro`, `ro_RO`, `ru`, `sk`, `sr` * `sv`, `sw`, `th`, `tk`, `tr`, `uk`, `ur` * `vi`, `zh`, `zh_CN`, `zh_HK`, `hr` diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle.kts similarity index 69% rename from example/android/app/build.gradle rename to example/android/app/build.gradle.kts index 1e62647a1..760f25fc8 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle.kts @@ -1,8 +1,7 @@ plugins { - id "com.android.application" - id "kotlin-android" + id("com.android.application") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. - id "dev.flutter.flutter-gradle-plugin" + id("dev.flutter.flutter-gradle-plugin") } android { @@ -15,10 +14,6 @@ android { targetCompatibility = JavaVersion.VERSION_17 } - kotlinOptions { - jvmTarget = JavaVersion.VERSION_17.toString() - } - defaultConfig { applicationId = "dev.flutterquill.flutter_quill_example" minSdk = flutter.minSdkVersion @@ -29,11 +24,18 @@ android { buildTypes { release { - signingConfig = signingConfigs.debug + // Signing with the debug keys for now, so `flutter run --release` works. + signingConfig = signingConfigs.getByName("debug") } } } +kotlin { + compilerOptions { + jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 + } +} + flutter { source = "../.." } diff --git a/example/android/build.gradle b/example/android/build.gradle deleted file mode 100644 index d2ffbffa4..000000000 --- a/example/android/build.gradle +++ /dev/null @@ -1,18 +0,0 @@ -allprojects { - repositories { - google() - mavenCentral() - } -} - -rootProject.buildDir = "../build" -subprojects { - project.buildDir = "${rootProject.buildDir}/${project.name}" -} -subprojects { - project.evaluationDependsOn(":app") -} - -tasks.register("clean", Delete) { - delete rootProject.buildDir -} diff --git a/example/android/build.gradle.kts b/example/android/build.gradle.kts new file mode 100644 index 000000000..dbee657bb --- /dev/null +++ b/example/android/build.gradle.kts @@ -0,0 +1,24 @@ +allprojects { + repositories { + google() + mavenCentral() + } +} + +val newBuildDir: Directory = + rootProject.layout.buildDirectory + .dir("../../build") + .get() +rootProject.layout.buildDirectory.value(newBuildDir) + +subprojects { + val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) + project.layout.buildDirectory.value(newSubprojectBuildDir) +} +subprojects { + project.evaluationDependsOn(":app") +} + +tasks.register("clean") { + delete(rootProject.layout.buildDirectory) +} diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 259717082..4147ba381 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,7 @@ org.gradle.jvmargs=-Xmx4G -XX:MaxMetaspaceSize=2G -XX:+HeapDumpOnOutOfMemoryError android.useAndroidX=true android.enableJetifier=true +# This builtInKotlin flag was added automatically by Flutter migrator +android.builtInKotlin=false +# This newDsl flag was added automatically by Flutter migrator +android.newDsl=false diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index ca025c83a..a35649f5f 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-all.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/example/android/settings.gradle b/example/android/settings.gradle deleted file mode 100644 index ff03f22be..000000000 --- a/example/android/settings.gradle +++ /dev/null @@ -1,25 +0,0 @@ -pluginManagement { - def flutterSdkPath = { - def properties = new Properties() - file("local.properties").withInputStream { properties.load(it) } - def flutterSdkPath = properties.getProperty("flutter.sdk") - assert flutterSdkPath != null, "flutter.sdk not set in local.properties" - return flutterSdkPath - }() - - includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") - - repositories { - google() - mavenCentral() - gradlePluginPortal() - } -} - -plugins { - id "dev.flutter.flutter-plugin-loader" version "1.0.0" - id "com.android.application" version "8.11.1" apply false - id "org.jetbrains.kotlin.android" version "2.2.20" apply false -} - -include ":app" diff --git a/example/android/settings.gradle.kts b/example/android/settings.gradle.kts new file mode 100644 index 000000000..c21f0c5b4 --- /dev/null +++ b/example/android/settings.gradle.kts @@ -0,0 +1,26 @@ +pluginManagement { + val flutterSdkPath = + run { + val properties = java.util.Properties() + file("local.properties").inputStream().use { properties.load(it) } + val flutterSdkPath = properties.getProperty("flutter.sdk") + require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" } + flutterSdkPath + } + + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") + + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} + +plugins { + id("dev.flutter.flutter-plugin-loader") version "1.0.0" + id("com.android.application") version "9.0.1" apply false + id("org.jetbrains.kotlin.android") version "2.3.20" apply false +} + +include(":app") diff --git a/example/integration_test/flutter_quill_integration_test.dart b/example/integration_test/flutter_quill_integration_test.dart index 6676238db..b456677e3 100644 --- a/example/integration_test/flutter_quill_integration_test.dart +++ b/example/integration_test/flutter_quill_integration_test.dart @@ -7,17 +7,18 @@ import 'package:integration_test/integration_test.dart'; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - testWidgets('$QuillEditor renders and handles input without crashing', - (tester) async { + testWidgets('$QuillEditor renders and handles input without crashing', ( + tester, + ) async { // This test ensures that the QuillEditor can be created and accepts input // without crashing on any platform. // // Example fix: https://github.com/singerdmx/flutter-quill/pull/2579 final controller = QuillController.basic(); - await tester.pumpWidget(MaterialApp( - home: QuillEditor.basic(controller: controller), - )); + await tester.pumpWidget( + MaterialApp(home: QuillEditor.basic(controller: controller)), + ); // Simulate text input to trigger user interactions. await tester.quillEnterText(find.byType(QuillEditor), 'sample text\n'); diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index ec97fc6f3..592ceee85 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Flutter/Release.xcconfig b/example/ios/Flutter/Release.xcconfig index c4855bfe2..592ceee85 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile deleted file mode 100644 index d97f17e22..000000000 --- a/example/ios/Podfile +++ /dev/null @@ -1,44 +0,0 @@ -# Uncomment this line to define a global platform for your project -# platform :ios, '12.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_ios_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_ios_build_settings(target) - end -end diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index 703875deb..9e8120505 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -10,10 +10,8 @@ 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 6AB2329557D1981D6B17B057 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04DE71649CFF61B6EA443C6C /* Pods_Runner.framework */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 95A4F67B5B7C7331534DAE52 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7CC42284313298068A87F511 /* Pods_RunnerTests.framework */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; @@ -43,19 +41,14 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 04DE71649CFF61B6EA443C6C /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 1E6E80686AEE93DAC1D78B29 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 6F7E0A20E20DB7725721ACD2 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - 71300142A7AF02C530456ED5 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; - 7CC42284313298068A87F511 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -63,9 +56,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9DD762FA5857F2B531FF16E5 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - CFC3FEF6AFF3650E4EE6A41D /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; - DD86B4B806817CB345ACF05D /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -73,7 +63,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 95A4F67B5B7C7331534DAE52 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -82,22 +71,12 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - 6AB2329557D1981D6B17B057 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 02F746990DACB0D119F72F34 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 04DE71649CFF61B6EA443C6C /* Pods_Runner.framework */, - 7CC42284313298068A87F511 /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( @@ -106,20 +85,6 @@ path = RunnerTests; sourceTree = ""; }; - 6FE303D04C9AABB6B0EEFED3 /* Pods */ = { - isa = PBXGroup; - children = ( - 6F7E0A20E20DB7725721ACD2 /* Pods-Runner.debug.xcconfig */, - 71300142A7AF02C530456ED5 /* Pods-Runner.release.xcconfig */, - 1E6E80686AEE93DAC1D78B29 /* Pods-Runner.profile.xcconfig */, - CFC3FEF6AFF3650E4EE6A41D /* Pods-RunnerTests.debug.xcconfig */, - 9DD762FA5857F2B531FF16E5 /* Pods-RunnerTests.release.xcconfig */, - DD86B4B806817CB345ACF05D /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; 9740EEB11CF90186004384FC /* Flutter */ = { isa = PBXGroup; children = ( @@ -138,8 +103,6 @@ 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, 331C8082294A63A400263BE5 /* RunnerTests */, - 6FE303D04C9AABB6B0EEFED3 /* Pods */, - 02F746990DACB0D119F72F34 /* Frameworks */, ); sourceTree = ""; }; @@ -174,7 +137,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - 7E4B580A265C6F44F99FF718 /* [CP] Check Pods Manifest.lock */, 331C807D294A63A400263BE5 /* Sources */, 331C807F294A63A400263BE5 /* Resources */, 940D3C4C6DD542413E14FFBD /* Frameworks */, @@ -193,7 +155,6 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - CF9215DAB0314651DACBA279 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, @@ -293,28 +254,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 7E4B580A265C6F44F99FF718 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -330,28 +269,6 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; - CF9215DAB0314651DACBA279 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -478,7 +395,6 @@ }; 331C8088294A63A400263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = CFC3FEF6AFF3650E4EE6A41D /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -496,7 +412,6 @@ }; 331C8089294A63A400263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9DD762FA5857F2B531FF16E5 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; @@ -512,7 +427,6 @@ }; 331C808A294A63A400263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = DD86B4B806817CB345ACF05D /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CODE_SIGN_STYLE = Automatic; diff --git a/example/lib/custom_toolbar.dart b/example/lib/custom_toolbar.dart index 5b308ac58..b84961b9c 100644 --- a/example/lib/custom_toolbar.dart +++ b/example/lib/custom_toolbar.dart @@ -16,14 +16,8 @@ class CustomToolbar extends StatelessWidget { scrollDirection: Axis.horizontal, child: Wrap( children: [ - QuillToolbarHistoryButton( - isUndo: true, - controller: controller, - ), - QuillToolbarHistoryButton( - isUndo: false, - controller: controller, - ), + QuillToolbarHistoryButton(isUndo: true, controller: controller), + QuillToolbarHistoryButton(isUndo: false, controller: controller), QuillToolbarToggleStyleButton( options: const QuillToolbarToggleStyleButtonOptions(), controller: controller, @@ -38,40 +32,22 @@ class CustomToolbar extends StatelessWidget { controller: controller, attribute: Attribute.underline, ), - QuillToolbarClearFormatButton( - controller: controller, - ), + QuillToolbarClearFormatButton(controller: controller), const VerticalDivider(), - QuillToolbarImageButton( - controller: controller, - ), - QuillToolbarCameraButton( - controller: controller, - ), - QuillToolbarVideoButton( - controller: controller, - ), + QuillToolbarImageButton(controller: controller), + QuillToolbarCameraButton(controller: controller), + QuillToolbarVideoButton(controller: controller), const VerticalDivider(), - QuillToolbarColorButton( - controller: controller, - isBackground: false, - ), - QuillToolbarColorButton( - controller: controller, - isBackground: true, - ), + QuillToolbarColorButton(controller: controller, isBackground: false), + QuillToolbarColorButton(controller: controller, isBackground: true), const VerticalDivider(), - QuillToolbarSelectHeaderStyleDropdownButton( - controller: controller, - ), + QuillToolbarSelectHeaderStyleDropdownButton(controller: controller), const VerticalDivider(), QuillToolbarSelectLineHeightStyleDropdownButton( controller: controller, ), const VerticalDivider(), - QuillToolbarToggleCheckListButton( - controller: controller, - ), + QuillToolbarToggleCheckListButton(controller: controller), QuillToolbarToggleStyleButton( controller: controller, attribute: Attribute.ol, @@ -88,14 +64,8 @@ class CustomToolbar extends StatelessWidget { controller: controller, attribute: Attribute.blockQuote, ), - QuillToolbarIndentButton( - controller: controller, - isIncrease: true, - ), - QuillToolbarIndentButton( - controller: controller, - isIncrease: false, - ), + QuillToolbarIndentButton(controller: controller, isIncrease: true), + QuillToolbarIndentButton(controller: controller, isIncrease: false), const VerticalDivider(), QuillToolbarLinkStyleButton(controller: controller), ], diff --git a/example/lib/main.dart b/example/lib/main.dart index bb9ead364..5aef6eef2 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -42,29 +42,30 @@ class HomePage extends StatefulWidget { class _HomePageState extends State { final QuillController _controller = () { return QuillController.basic( - config: QuillControllerConfig( - clipboardConfig: QuillClipboardConfig( - enableExternalRichPaste: true, - onImagePaste: (imageBytes) async { - if (kIsWeb) { - // Dart IO is unsupported on the web. - return null; - } - // Save the image somewhere and return the image URL that will be - // stored in the Quill Delta JSON (the document). - final newFileName = - 'image-file-${DateTime.now().toIso8601String()}.png'; - final newPath = path.join( - io.Directory.systemTemp.path, - newFileName, - ); - final file = await io.File( - newPath, - ).writeAsBytes(imageBytes, flush: true); - return file.path; - }, + config: QuillControllerConfig( + clipboardConfig: QuillClipboardConfig( + enableExternalRichPaste: true, + onImagePaste: (imageBytes) async { + if (kIsWeb) { + // Dart IO is unsupported on the web. + return null; + } + // Save the image somewhere and return the image URL that will be + // stored in the Quill Delta JSON (the document). + final newFileName = + 'image-file-${DateTime.now().toIso8601String()}.png'; + final newPath = path.join( + io.Directory.systemTemp.path, + newFileName, + ); + final file = await io.File( + newPath, + ).writeAsBytes(imageBytes, flush: true); + return file.path; + }, + ), ), - )); + ); }(); final QuillFocusNode _editorFocusNode = QuillFocusNode(); final ScrollController _editorScrollController = ScrollController(); @@ -93,9 +94,13 @@ class _HomePageState extends State { icon: const Icon(Icons.output), tooltip: 'Print Delta JSON to log', onPressed: () { - ScaffoldMessenger.of(context).showSnackBar(const SnackBar( - content: - Text('The JSON Delta has been printed to the console.'))); + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text( + 'The JSON Delta has been printed to the console.', + ), + ), + ); debugPrint(jsonEncode(_controller.document.toDelta().toJson())); }, ), @@ -141,15 +146,14 @@ class _HomePageState extends State { config: QuillSimpleToolbarConfig( embedButtons: FlutterQuillEmbeds.toolbarButtons(), showClipboardPaste: true, + showDirection: true, customButtons: [ QuillToolbarCustomButtonOptions( icon: const Icon(Icons.add_alarm_rounded), onPressed: () { _controller.document.insert( _controller.selection.extentOffset, - TimeStampEmbed( - DateTime.now().toString(), - ), + TimeStampEmbed(DateTime.now().toString()), ); _controller.updateSelection( @@ -167,7 +171,7 @@ class _HomePageState extends State { final isDesktop = { TargetPlatform.linux, TargetPlatform.windows, - TargetPlatform.macOS + TargetPlatform.macOS, }.contains(defaultTargetPlatform); if (isDesktop) { _editorFocusNode.requestFocus(); @@ -232,9 +236,7 @@ class _HomePageState extends State { } class TimeStampEmbed extends Embeddable { - const TimeStampEmbed( - String value, - ) : super(timeStampType, value); + const TimeStampEmbed(String value) : super(timeStampType, value); static const String timeStampType = 'timeStamp'; @@ -254,10 +256,7 @@ class TimeStampEmbedBuilder extends EmbedBuilder { } @override - Widget build( - BuildContext context, - EmbedContext embedContext, - ) { + Widget build(BuildContext context, EmbedContext embedContext) { return Row( children: [ const Icon(Icons.access_time_rounded), diff --git a/example/lib/quill_delta_sample.dart b/example/lib/quill_delta_sample.dart index 616ac8453..07838b72d 100644 --- a/example/lib/quill_delta_sample.dart +++ b/example/lib/quill_delta_sample.dart @@ -6,290 +6,290 @@ const kQuillDefaultSample = [ 'attributes': { 'width': '100', 'height': '100', - 'style': 'width:500px; height:350px;' - } + 'style': 'width:500px; height:350px;', + }, }, {'insert': 'Flutter Quill'}, { 'attributes': {'header': 1}, - 'insert': '\n' + 'insert': '\n', }, { 'insert': { 'video': - 'https://www.youtube.com/watch?v=V4hgdKhIqtc&list=PLbhaS_83B97s78HsDTtplRTEhcFsqSqIK&index=1' - } + 'https://www.youtube.com/watch?v=V4hgdKhIqtc&list=PLbhaS_83B97s78HsDTtplRTEhcFsqSqIK&index=1', + }, }, { 'insert': { 'video': - 'https://user-images.githubusercontent.com/122956/126238875-22e42501-ad41-4266-b1d6-3f89b5e3b79b.mp4' - } + 'https://user-images.githubusercontent.com/122956/126238875-22e42501-ad41-4266-b1d6-3f89b5e3b79b.mp4', + }, }, {'insert': '\nRich text editor for Flutter'}, { 'attributes': {'header': 2}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Quill component for Flutter'}, { 'attributes': {'header': 3}, - 'insert': '\n' + 'insert': '\n', }, { 'attributes': {'link': 'https://bulletjournal.us/home/index.html'}, - 'insert': 'Bullet Journal' + 'insert': 'Bullet Journal', }, { 'insert': - ':\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders' + ':\nTrack personal and group journals (ToDo, Note, Ledger) from multiple views with timely reminders', }, { 'attributes': {'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, { 'insert': - 'Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices' + 'Share your tasks and notes with teammates, and see changes as they happen in real-time, across all devices', }, { 'attributes': {'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Check out what you and your teammates are working on each day'}, { 'attributes': {'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': '\nSplitting bills with friends can never be easier.'}, { 'attributes': {'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Start creating a group and invite your friends to join.'}, { 'attributes': {'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Create a BuJo of Ledger type to see expense or balance summary.'}, { 'attributes': {'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, { 'insert': - '\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s).' + '\nAttach one or multiple labels to tasks, notes or transactions. Later you can track them just using the label(s).', }, { 'attributes': {'blockquote': true}, - 'insert': '\n' + 'insert': '\n', }, {'insert': "\nvar BuJo = 'Bullet' + 'Journal'"}, { 'attributes': {'code-block': true}, - 'insert': '\n' + 'insert': '\n', }, {'insert': '\nStart tracking in your browser'}, { 'attributes': {'indent': 1}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Stop the timer on your phone'}, { 'attributes': {'indent': 1}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'All your time entries are synced'}, { 'attributes': {'indent': 2}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'between the phone apps'}, { 'attributes': {'indent': 2}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'and the website.'}, { 'attributes': {'indent': 3}, - 'insert': '\n' + 'insert': '\n', }, {'insert': '\n'}, {'insert': '\nCenter Align'}, { 'attributes': {'align': 'center'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Right Align'}, { 'attributes': {'align': 'right'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Justify Align'}, { 'attributes': {'align': 'justify'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Have trouble finding things? '}, { 'attributes': {'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Just type in the search bar'}, { 'attributes': {'indent': 1, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'and easily find contents'}, { 'attributes': {'indent': 2, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'across projects or folders.'}, { 'attributes': {'indent': 2, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'It matches text in your note or task.'}, { 'attributes': {'indent': 1, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Enable reminders so that you will get notified by'}, { 'attributes': {'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'email'}, { 'attributes': {'indent': 1, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'message on your phone'}, { 'attributes': {'indent': 1, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'popup on the web site'}, { 'attributes': {'indent': 1, 'list': 'ordered'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Create a BuJo serving as project or folder'}, { 'attributes': {'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Organize your'}, { 'attributes': {'indent': 1, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'tasks'}, { 'attributes': {'indent': 2, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'notes'}, { 'attributes': {'indent': 2, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'transactions'}, { 'attributes': {'indent': 2, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'under BuJo '}, { 'attributes': {'indent': 3, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'See them in Calendar'}, { 'attributes': {'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'or hierarchical view'}, { 'attributes': {'indent': 1, 'list': 'bullet'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'this is a check list'}, { 'attributes': {'list': 'checked'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'this is a uncheck list'}, { 'attributes': {'list': 'unchecked'}, - 'insert': '\n' + 'insert': '\n', }, {'insert': 'Font '}, { 'attributes': {'font': 'sans-serif'}, - 'insert': 'Sans Serif' + 'insert': 'Sans Serif', }, {'insert': ' '}, { 'attributes': {'font': 'serif'}, - 'insert': 'Serif' + 'insert': 'Serif', }, {'insert': ' '}, { 'attributes': {'font': 'monospace'}, - 'insert': 'Monospace' + 'insert': 'Monospace', }, {'insert': ' Size '}, { 'attributes': {'size': 'small'}, - 'insert': 'Small' + 'insert': 'Small', }, {'insert': ' '}, { 'attributes': {'size': 'large'}, - 'insert': 'Large' + 'insert': 'Large', }, {'insert': ' '}, { 'attributes': {'size': 'huge'}, - 'insert': 'Huge' + 'insert': 'Huge', }, { 'attributes': {'size': '15.0'}, - 'insert': 'font size 15' + 'insert': 'font size 15', }, {'insert': ' '}, { 'attributes': {'size': '35'}, - 'insert': 'font size 35' + 'insert': 'font size 35', }, {'insert': ' '}, { 'attributes': {'size': '20'}, - 'insert': 'font size 20' + 'insert': 'font size 20', }, { 'attributes': {'token': 'built_in'}, - 'insert': ' diff' + 'insert': ' diff', }, { 'attributes': {'token': 'operator'}, - 'insert': '-match' + 'insert': '-match', }, { 'attributes': {'token': 'literal'}, - 'insert': '-patch' + 'insert': '-patch', }, { 'insert': { 'image': - 'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg' + 'https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg', }, 'attributes': { 'width': '230', - 'style': 'display: block; margin: auto; width: 500px;' - } + 'style': 'display: block; margin: auto; width: 500px;', + }, }, - {'insert': '\n'} + {'insert': '\n'}, ]; diff --git a/example/linux/CMakeLists.txt b/example/linux/CMakeLists.txt index 280c69355..d7ee3300d 100644 --- a/example/linux/CMakeLists.txt +++ b/example/linux/CMakeLists.txt @@ -1,5 +1,5 @@ # Project-level configuration. -cmake_minimum_required(VERSION 3.10) +cmake_minimum_required(VERSION 3.13) project(runner LANGUAGES CXX) # The name of the executable created for the application. Change this to change diff --git a/example/macos/Flutter/Flutter-Debug.xcconfig b/example/macos/Flutter/Flutter-Debug.xcconfig index 4b81f9b2d..c2efd0b60 100644 --- a/example/macos/Flutter/Flutter-Debug.xcconfig +++ b/example/macos/Flutter/Flutter-Debug.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/Flutter-Release.xcconfig b/example/macos/Flutter/Flutter-Release.xcconfig index 5caa9d157..c2efd0b60 100644 --- a/example/macos/Flutter/Flutter-Release.xcconfig +++ b/example/macos/Flutter/Flutter-Release.xcconfig @@ -1,2 +1 @@ -#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "ephemeral/Flutter-Generated.xcconfig" diff --git a/example/macos/Flutter/GeneratedPluginRegistrant.swift b/example/macos/Flutter/GeneratedPluginRegistrant.swift index 3bcdb912f..9bf7023a7 100644 --- a/example/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/example/macos/Flutter/GeneratedPluginRegistrant.swift @@ -14,5 +14,5 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) QuillNativeBridgePlugin.register(with: registry.registrar(forPlugin: "QuillNativeBridgePlugin")) UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin")) - FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) + VideoPlayerPlugin.register(with: registry.registrar(forPlugin: "VideoPlayerPlugin")) } diff --git a/example/macos/Podfile b/example/macos/Podfile deleted file mode 100644 index dbccf89c9..000000000 --- a/example/macos/Podfile +++ /dev/null @@ -1,43 +0,0 @@ -platform :osx, '11.0' - -# CocoaPods analytics sends network stats synchronously affecting flutter build latency. -ENV['COCOAPODS_DISABLE_STATS'] = 'true' - -project 'Runner', { - 'Debug' => :debug, - 'Profile' => :release, - 'Release' => :release, -} - -def flutter_root - generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__) - unless File.exist?(generated_xcode_build_settings_path) - raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first" - end - - File.foreach(generated_xcode_build_settings_path) do |line| - matches = line.match(/FLUTTER_ROOT\=(.*)/) - return matches[1].strip if matches - end - raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\"" -end - -require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) - -flutter_macos_podfile_setup - -target 'Runner' do - use_frameworks! - use_modular_headers! - - flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__)) - target 'RunnerTests' do - inherit! :search_paths - end -end - -post_install do |installer| - installer.pods_project.targets.each do |target| - flutter_additional_macos_build_settings(target) - end -end diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock deleted file mode 100644 index ce458c1e1..000000000 --- a/example/macos/Podfile.lock +++ /dev/null @@ -1,16 +0,0 @@ -PODS: - - FlutterMacOS (1.0.0) - -DEPENDENCIES: - - FlutterMacOS (from `Flutter/ephemeral`) - -EXTERNAL SOURCES: - FlutterMacOS: - :path: Flutter/ephemeral - -SPEC CHECKSUMS: - FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - -PODFILE CHECKSUM: c2e95c8c0fe03c5c57e438583cae4cc732296009 - -COCOAPODS: 1.16.2 diff --git a/example/macos/Runner.xcodeproj/project.pbxproj b/example/macos/Runner.xcodeproj/project.pbxproj index 4d1aed5d2..b8d2ef675 100644 --- a/example/macos/Runner.xcodeproj/project.pbxproj +++ b/example/macos/Runner.xcodeproj/project.pbxproj @@ -28,8 +28,6 @@ 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 78A3181F2AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage */; }; - 8E3774B08A93A8D54E597870 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C6627E85DE71088607AABDE /* Pods_RunnerTests.framework */; }; - CBFCAD814543F1F46CF7EB02 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8B7FF1B5F74BB7EE9AEAADC6 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -63,7 +61,6 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 2D37AD789A348E0ED9F8CF95 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; 331C80D5294CF71000263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 331C80D7294CF71000263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = ""; }; 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; @@ -80,15 +77,9 @@ 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; - 4C6627E85DE71088607AABDE /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; - 7C0DBFDB60FEA7B74A31DCD6 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = ""; }; - 8B7FF1B5F74BB7EE9AEAADC6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; - 9DFF9F1E9DBEBF88D3130224 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = ""; }; - A469B99595D46C18EEEAB749 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; - B94E8E7585B52349284FEC7C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; - C0A4ACB16DD0C36754B830CC /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = ""; }; + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = FlutterGeneratedPluginSwiftPackage; path = ephemeral/Packages/FlutterGeneratedPluginSwiftPackage; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -96,7 +87,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 8E3774B08A93A8D54E597870 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -105,7 +95,6 @@ buildActionMask = 2147483647; files = ( 78A318202AECB46A00862997 /* FlutterGeneratedPluginSwiftPackage in Frameworks */, - CBFCAD814543F1F46CF7EB02 /* Pods_Runner.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -138,8 +127,6 @@ 33CEB47122A05771004F2AC0 /* Flutter */, 331C80D6294CF71000263BE5 /* RunnerTests */, 33CC10EE2044A3C60003C045 /* Products */, - D73912EC22F37F3D000D13A0 /* Frameworks */, - FA0924593192DA52F60E2347 /* Pods */, ); sourceTree = ""; }; @@ -166,6 +153,7 @@ 33CEB47122A05771004F2AC0 /* Flutter */ = { isa = PBXGroup; children = ( + 78E0A7A72DC9AD7400C4905E /* FlutterGeneratedPluginSwiftPackage */, 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, @@ -187,29 +175,6 @@ path = Runner; sourceTree = ""; }; - D73912EC22F37F3D000D13A0 /* Frameworks */ = { - isa = PBXGroup; - children = ( - 8B7FF1B5F74BB7EE9AEAADC6 /* Pods_Runner.framework */, - 4C6627E85DE71088607AABDE /* Pods_RunnerTests.framework */, - ); - name = Frameworks; - sourceTree = ""; - }; - FA0924593192DA52F60E2347 /* Pods */ = { - isa = PBXGroup; - children = ( - A469B99595D46C18EEEAB749 /* Pods-Runner.debug.xcconfig */, - B94E8E7585B52349284FEC7C /* Pods-Runner.release.xcconfig */, - 2D37AD789A348E0ED9F8CF95 /* Pods-Runner.profile.xcconfig */, - C0A4ACB16DD0C36754B830CC /* Pods-RunnerTests.debug.xcconfig */, - 9DFF9F1E9DBEBF88D3130224 /* Pods-RunnerTests.release.xcconfig */, - 7C0DBFDB60FEA7B74A31DCD6 /* Pods-RunnerTests.profile.xcconfig */, - ); - name = Pods; - path = Pods; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -217,7 +182,6 @@ isa = PBXNativeTarget; buildConfigurationList = 331C80DE294CF71000263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; buildPhases = ( - C27377F9664DD6D00B8324C5 /* [CP] Check Pods Manifest.lock */, 331C80D1294CF70F00263BE5 /* Sources */, 331C80D2294CF70F00263BE5 /* Frameworks */, 331C80D3294CF70F00263BE5 /* Resources */, @@ -236,7 +200,6 @@ isa = PBXNativeTarget; buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - ADA7845785713A6AD96ACC83 /* [CP] Check Pods Manifest.lock */, 33CC10E92044A3C60003C045 /* Sources */, 33CC10EA2044A3C60003C045 /* Frameworks */, 33CC10EB2044A3C60003C045 /* Resources */, @@ -368,50 +331,6 @@ shellPath = /bin/sh; shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh && touch Flutter/ephemeral/tripwire"; }; - ADA7845785713A6AD96ACC83 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; - C27377F9664DD6D00B8324C5 /* [CP] Check Pods Manifest.lock */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - ); - inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", - ); - name = "[CP] Check Pods Manifest.lock"; - outputFileListPaths = ( - ); - outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; - }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -463,7 +382,6 @@ /* Begin XCBuildConfiguration section */ 331C80DB294CF71000263BE5 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = C0A4ACB16DD0C36754B830CC /* Pods-RunnerTests.debug.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -478,7 +396,6 @@ }; 331C80DC294CF71000263BE5 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9DFF9F1E9DBEBF88D3130224 /* Pods-RunnerTests.release.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; @@ -493,7 +410,6 @@ }; 331C80DD294CF71000263BE5 /* Profile */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7C0DBFDB60FEA7B74A31DCD6 /* Pods-RunnerTests.profile.xcconfig */; buildSettings = { BUNDLE_LOADER = "$(TEST_HOST)"; CURRENT_PROJECT_VERSION = 1; diff --git a/example/pubspec.lock b/example/pubspec.lock index b8f5b9e17..c0c569062 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.13.1" boolean_selector: dependency: transitive description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: characters - sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.4.1" charcode: dependency: transitive description: @@ -61,10 +61,10 @@ packages: dependency: transitive description: name: cross_file - sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + sha256: "28bb3ae56f117b5aec029d702a90f57d285cd975c3c5c281eaca38dbc47c5937" url: "https://pub.dev" source: hosted - version: "0.3.4+2" + version: "0.3.5+2" csslib: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: ffi - sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" + sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45" url: "https://pub.dev" source: hosted - version: "2.1.4" + version: "2.2.0" file: dependency: transitive description: @@ -117,34 +117,34 @@ packages: dependency: transitive description: name: file_selector_linux - sha256: "54cbbd957e1156d29548c7d9b9ec0c0ebb6de0a90452198683a7d23aed617a33" + sha256: "2567f398e06ac72dcf2e98a0c95df2a9edd03c2c2e0cacd4780f20cdf56263a0" url: "https://pub.dev" source: hosted - version: "0.9.3+2" + version: "0.9.4" file_selector_macos: dependency: transitive description: name: file_selector_macos - sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" + sha256: "5e0bbe9c312416f1787a68259ea1505b52f258c587f12920422671807c4d618a" url: "https://pub.dev" source: hosted - version: "0.9.4+3" + version: "0.9.5" file_selector_platform_interface: dependency: transitive description: name: file_selector_platform_interface - sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b + sha256: "35e0bd61ebcdb91a3505813b055b09b79dfdc7d0aee9c09a7ba59ae4bb13dc85" url: "https://pub.dev" source: hosted - version: "2.6.2" + version: "2.7.0" file_selector_windows: dependency: transitive description: name: file_selector_windows - sha256: "320fcfb6f33caa90f0b58380489fc5ac05d99ee94b61aa96ec2bff0ba81d3c2b" + sha256: "62197474ae75893a62df75939c777763d39c2bc5f73ce5b88497208bc269abfd" url: "https://pub.dev" source: hosted - version: "0.9.3+4" + version: "0.9.3+5" flutter: dependency: "direct main" description: flutter @@ -220,25 +220,25 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e + sha256: "38d1c268de9097ff59cf0e844ac38759fc78f76836d37edad06fa21e182055a0" url: "https://pub.dev" source: hosted - version: "2.0.28" + version: "2.0.34" flutter_quill: dependency: "direct main" description: path: ".." relative: true source: path - version: "11.5.0" + version: "11.5.2" flutter_quill_delta_from_html: dependency: transitive description: name: flutter_quill_delta_from_html - sha256: "4597bd0853a704696837aa6b05cffd851f587b176204c234edddfed1c1862a09" + sha256: "0eb801ea8dd498cadc057507af5da794d4c9599ce58b2569cb3d4bb53ba8bed2" url: "https://pub.dev" source: hosted - version: "1.5.2" + version: "1.5.3" flutter_quill_extensions: dependency: "direct main" description: @@ -280,10 +280,10 @@ packages: dependency: transitive description: name: http - sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" + sha256: "87721a4a50b19c7f1d49001e51409bddc46303966ce89a65af4f4e6004896412" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "1.6.0" http_parser: dependency: transitive description: @@ -296,66 +296,66 @@ packages: dependency: transitive description: name: image_picker - sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a" + sha256: "91c025426c2881c551100bce834e201c835a170151545f58d17da5180ca7d9ac" url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.2" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "6fae381e6af2bbe0365a5e4ce1db3959462fa0c4d234facf070746024bb80c8d" + sha256: d5b3e1774af29c9ab00103afb0d4614070f924d2e0057ac867ec98800114793f url: "https://pub.dev" source: hosted - version: "0.8.12+24" + version: "0.8.13+17" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: "717eb042ab08c40767684327be06a5d8dbb341fe791d514e4b92c7bbe1b7bb83" + sha256: "66257a3191ab360d23a55c8241c91a6e329d31e94efa7be9cf7a212e65850214" url: "https://pub.dev" source: hosted - version: "3.0.6" + version: "3.1.1" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "05da758e67bc7839e886b3959848aa6b44ff123ab4b28f67891008afe8ef9100" + sha256: b9c4a438a9ff4f60808c9cf0039b93a42bb6c2211ef6ebb647394b2b3fa84588 url: "https://pub.dev" source: hosted - version: "0.8.12+2" + version: "0.8.13+6" image_picker_linux: dependency: transitive description: name: image_picker_linux - sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" + sha256: "1f81c5f2046b9ab724f85523e4af65be1d47b038160a8c8deed909762c308ed4" url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2" image_picker_macos: dependency: transitive description: name: image_picker_macos - sha256: "1b90ebbd9dcf98fb6c1d01427e49a55bd96b5d67b8c67cf955d60a5de74207c1" + sha256: "86f0f15a309de7e1a552c12df9ce5b59fe927e71385329355aec4776c6a8ec91" url: "https://pub.dev" source: hosted - version: "0.2.1+2" + version: "0.2.2+1" image_picker_platform_interface: dependency: transitive description: name: image_picker_platform_interface - sha256: "886d57f0be73c4b140004e78b9f28a8914a09e50c2d816bdd0520051a71236a0" + sha256: "567e056716333a1647c64bb6bd873cff7622233a5c3f694be28a583d4715690c" url: "https://pub.dev" source: hosted - version: "2.10.1" + version: "2.11.1" image_picker_windows: dependency: transitive description: name: image_picker_windows - sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb" + sha256: d248c86554a72b5495a31c56f060cf73a41c7ff541689327b1a7dbccc33adfae url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.2" integration_test: dependency: "direct dev" description: flutter @@ -405,34 +405,34 @@ packages: dependency: transitive description: name: markdown - sha256: "935e23e1ff3bc02d390bad4d4be001208ee92cc217cb5b5a6c19bc14aaa318c1" + sha256: ee85086ad7698b42522c6ad42fe195f1b9898e4d974a1af4576c1a3a176cada9 url: "https://pub.dev" source: hosted - version: "7.3.0" + version: "7.3.1" matcher: dependency: transitive description: name: matcher - sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861 url: "https://pub.dev" source: hosted - version: "0.12.17" + version: "0.12.19" material_color_utilities: dependency: transitive description: name: material_color_utilities - sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b" url: "https://pub.dev" source: hosted - version: "0.11.1" + version: "0.13.0" meta: dependency: transitive description: name: meta - sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394" + sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349" url: "https://pub.dev" source: hosted - version: "1.17.0" + version: "1.18.0" mime: dependency: transitive description: @@ -477,10 +477,10 @@ packages: dependency: transitive description: name: process - sha256: "107d8be718f120bbba9dcd1e95e3bd325b1b4a4f07db64154635ba03f2567a0d" + sha256: c6248e4526673988586e8c00bb22a49210c258dc91df5227d5da9748ecf79744 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.0.5" quill_native_bridge: dependency: "direct overridden" description: @@ -570,10 +570,10 @@ packages: dependency: transitive description: name: source_span - sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab" url: "https://pub.dev" source: hosted - version: "1.10.1" + version: "1.10.2" stack_trace: dependency: transitive description: @@ -618,10 +618,10 @@ packages: dependency: transitive description: name: test_api - sha256: ab2726c1a94d3176a45960b6234466ec367179b87dd74f1611adb1f3b5fb9d55 + sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e" url: "https://pub.dev" source: hosted - version: "0.7.7" + version: "0.7.11" typed_data: dependency: transitive description: @@ -642,34 +642,34 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" + sha256: "3bb000251e55d4a209aa0e2e563309dc9bb2befea2295fd0cec1f51760aac572" url: "https://pub.dev" source: hosted - version: "6.3.16" + version: "6.3.29" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" + sha256: "580fe5dfb51671ae38191d316e027f6b76272b026370708c2d898799750a02b0" url: "https://pub.dev" source: hosted - version: "6.3.3" + version: "6.4.1" url_launcher_linux: dependency: transitive description: name: url_launcher_linux - sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935" + sha256: d5e14138b3bc193a0f63c10a53c94b91d399df0512b1f29b94a043db7482384a url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.2" url_launcher_macos: dependency: transitive description: name: url_launcher_macos - sha256: "17ba2000b847f334f16626a574c702b196723af2a289e7a93ffcb79acff855c2" + sha256: "368adf46f71ad3c21b8f06614adb38346f193f3a59ba8fe9a2fd74133070ba18" url: "https://pub.dev" source: hosted - version: "3.2.2" + version: "3.2.5" url_launcher_platform_interface: dependency: transitive description: @@ -682,18 +682,18 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" + sha256: "85c81589622fbc87c1c683aaea164d3604a7777495a79d91e39ffcdec39ddb34" url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.3" url_launcher_windows: dependency: transitive description: name: url_launcher_windows - sha256: "3284b6d2ac454cf34f114e1d3319866fdd1e19cdc329999057e44ffe936cfa77" + sha256: "712c70ab1b99744ff066053cbe3e80c73332b38d46e5e945c98689b2e66fc15f" url: "https://pub.dev" source: hosted - version: "3.1.4" + version: "3.1.5" vector_math: dependency: transitive description: @@ -706,34 +706,34 @@ packages: dependency: transitive description: name: video_player - sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" + sha256: "48a7bdaa38a3d50ec10c78627abdbfad863fdf6f0d6e08c7c3c040cfd80ae36f" url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.1" video_player_android: dependency: transitive description: name: video_player_android - sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" + sha256: "877a6c7ba772456077d7bfd71314629b3fe2b73733ce503fc77c3314d43a0ca0" url: "https://pub.dev" source: hosted - version: "2.8.7" + version: "2.9.5" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "9fedd55023249f3a02738c195c906b4e530956191febf0838e37d0dac912f953" + sha256: "9338f3ec22774f88146b22f13273a446719b1da010fd200c4d1d97802156ac58" url: "https://pub.dev" source: hosted - version: "2.8.0" + version: "2.9.7" video_player_platform_interface: dependency: transitive description: name: video_player_platform_interface - sha256: cf2a1d29a284db648fd66cbd18aacc157f9862d77d2cc790f6f9678a46c1db5a + sha256: "16eaed5268c571c31840dc58ef8da5f0cd4db2a98490c3b8f1cf70122546c6e0" url: "https://pub.dev" source: hosted - version: "6.4.0" + version: "6.7.0" video_player_web: dependency: transitive description: @@ -746,10 +746,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "15.2.0" web: dependency: transitive description: @@ -770,10 +770,10 @@ packages: dependency: transitive description: name: win32 - sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03" + sha256: d7cb55e04cd34096cd3a79b3330245f54cb96a370a1c27adb3c84b917de8b08e url: "https://pub.dev" source: hosted - version: "5.14.0" + version: "5.15.0" sdks: - dart: ">=3.8.0 <4.0.0" - flutter: ">=3.29.0" + dart: ">=3.12.0 <4.0.0" + flutter: ">=3.44.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 72f278819..d1923edd5 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,8 +4,8 @@ publish_to: 'none' version: 0.1.0+1 environment: - sdk: ^3.5.0 - flutter: ">=3.24.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: flutter: diff --git a/flutter_quill_extensions/CHANGELOG.md b/flutter_quill_extensions/CHANGELOG.md index d68bfd322..3297e7aa2 100644 --- a/flutter_quill_extensions/CHANGELOG.md +++ b/flutter_quill_extensions/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Updated minimum supported SDK version to Flutter 3.44/Dart 3.12 to align with `flutter_quill`. + ## [11.0.0] - 2025-02-16 > [!IMPORTANT] diff --git a/flutter_quill_extensions/lib/src/common/extensions/attribute.dart b/flutter_quill_extensions/lib/src/common/extensions/attribute.dart index 5331b552a..e4e0ea475 100644 --- a/flutter_quill_extensions/lib/src/common/extensions/attribute.dart +++ b/flutter_quill_extensions/lib/src/common/extensions/attribute.dart @@ -3,7 +3,7 @@ import 'package:flutter_quill/flutter_quill.dart' class FlutterAlignmentAttribute extends Attribute { const FlutterAlignmentAttribute(String? val) - : super('flutterAlignment', AttributeScope.ignore, val); + : super('flutterAlignment', AttributeScope.ignore, val); } extension AttributeExt on Attribute { diff --git a/flutter_quill_extensions/lib/src/common/extensions/controller_ext.dart b/flutter_quill_extensions/lib/src/common/extensions/controller_ext.dart index 45443c4da..68ac7aff8 100644 --- a/flutter_quill_extensions/lib/src/common/extensions/controller_ext.dart +++ b/flutter_quill_extensions/lib/src/common/extensions/controller_ext.dart @@ -3,31 +3,24 @@ import 'package:flutter_quill/flutter_quill.dart'; @Deprecated('Invalid extension') extension QuillControllerExt on QuillController { @Deprecated( - 'Invalid extension property and will be removed, use selection.baseOffset instead') + 'Invalid extension property and will be removed, use selection.baseOffset instead', + ) int get index => selection.baseOffset; @Deprecated( - 'Invalid extension property and will be removed, use selection.extentOffset - selection.baseOffset instead') + 'Invalid extension property and will be removed, use selection.extentOffset - selection.baseOffset instead', + ) int get length => selection.extentOffset - index; @Deprecated('Invalid extension method and will be removed.') - void insertImageBlock({ - required String imageSource, - }) { + void insertImageBlock({required String imageSource}) { this ..skipRequestKeyboard = true - ..replaceText( - index, - length, - BlockEmbed.image(imageSource), - null, - ) + ..replaceText(index, length, BlockEmbed.image(imageSource), null) ..moveCursorToPosition(index + 1); } @Deprecated('Invalid extension method and will be removed.') - void insertVideoBlock({ - required String videoUrl, - }) { + void insertVideoBlock({required String videoUrl}) { this ..skipRequestKeyboard = true ..replaceText(index, length, BlockEmbed.video(videoUrl), null) diff --git a/flutter_quill_extensions/lib/src/common/image_video_utils.dart b/flutter_quill_extensions/lib/src/common/image_video_utils.dart index 216566586..74cceb485 100644 --- a/flutter_quill_extensions/lib/src/common/image_video_utils.dart +++ b/flutter_quill_extensions/lib/src/common/image_video_utils.dart @@ -4,10 +4,7 @@ import 'package:flutter_quill/internal.dart'; import 'utils/patterns.dart'; -enum LinkType { - video, - image, -} +enum LinkType { video, image } class TypeLinkDialog extends StatefulWidget { const TypeLinkDialog({ diff --git a/flutter_quill_extensions/lib/src/common/utils/element_utils/element_utils.dart b/flutter_quill_extensions/lib/src/common/utils/element_utils/element_utils.dart index 86a95f461..2d1f5a9d5 100644 --- a/flutter_quill_extensions/lib/src/common/utils/element_utils/element_utils.dart +++ b/flutter_quill_extensions/lib/src/common/utils/element_utils/element_utils.dart @@ -8,18 +8,10 @@ import 'element_shared_utils.dart'; /// Theses properties are not officialy supported by quill js /// but they are only used in all platforms other than web /// and they will be stored in css style property so quill js ignore them -enum ExtraElementProperties { - deletable, -} +enum ExtraElementProperties { deletable } -( - ElementSize elementSize, - double? margin, - Alignment alignment, -) getElementAttributes( - Node node, - BuildContext context, -) { +(ElementSize elementSize, double? margin, Alignment alignment) +getElementAttributes(Node node, BuildContext context) { var elementSize = const ElementSize(null, null); var elementAlignment = Alignment.center; double? elementMargin; @@ -34,14 +26,10 @@ enum ExtraElementProperties { ); if (heightValue != null) { - elementSize = elementSize.copyWith( - height: heightValue, - ); + elementSize = elementSize.copyWith(height: heightValue); } if (widthValue != null) { - elementSize = elementSize.copyWith( - width: widthValue, - ); + elementSize = elementSize.copyWith(width: widthValue); } final cssStyle = node.style.attributes['style']; @@ -81,10 +69,7 @@ enum ExtraElementProperties { @immutable class ElementSize { - const ElementSize( - this.width, - this.height, - ); + const ElementSize(this.width, this.height); /// If non-null, requires the child to have exactly this width. /// If null, the child is free to choose its own width. @@ -94,13 +79,7 @@ class ElementSize { /// If null, the child is free to choose its own height. final double? height; - ElementSize copyWith({ - double? width, - double? height, - }) { - return ElementSize( - width ?? this.width, - height ?? this.height, - ); + ElementSize copyWith({double? width, double? height}) { + return ElementSize(width ?? this.width, height ?? this.height); } } diff --git a/flutter_quill_extensions/lib/src/common/utils/element_utils/element_web_utils.dart b/flutter_quill_extensions/lib/src/common/utils/element_utils/element_web_utils.dart index 091f1c319..b6d187b2e 100644 --- a/flutter_quill_extensions/lib/src/common/utils/element_utils/element_web_utils.dart +++ b/flutter_quill_extensions/lib/src/common/utils/element_utils/element_web_utils.dart @@ -5,14 +5,8 @@ import 'element_shared_utils.dart'; /// Prefer the width, and height from the css style attribute if exits /// it can be `auto` or `100px` so it's specific to HTML && CSS /// if not, we will use the one from attributes which is usually just an double -( - String height, - String width, - String margin, - String alignment, -) getWebElementAttributes( - Node node, -) { +(String height, String width, String margin, String alignment) +getWebElementAttributes(Node node) { var height = 'auto'; var width = 'auto'; // TODO(): Add support for margin and alignment diff --git a/flutter_quill_extensions/lib/src/common/utils/web/web_real.dart b/flutter_quill_extensions/lib/src/common/utils/web/web_real.dart index da3119b39..4097743cb 100644 --- a/flutter_quill_extensions/lib/src/common/utils/web/web_real.dart +++ b/flutter_quill_extensions/lib/src/common/utils/web/web_real.dart @@ -1,6 +1,7 @@ import 'package:web/web.dart'; import '../dart_ui/dart_ui_fake.dart' - if (dart.library.js_interop) '../dart_ui/dart_ui_real.dart' as ui; + if (dart.library.js_interop) '../dart_ui/dart_ui_real.dart' + as ui; void main(List args) { HTMLImageElement; @@ -31,16 +32,13 @@ void createHtmlIFrameElement({ required String margin, required String alignSelf, }) { - ui.PlatformViewRegistry().registerViewFactory( - src, - (id) { - return HTMLIFrameElement() - ..style.width = width - ..style.height = height - ..src = src - ..style.border = 'none' - ..style.margin = margin - ..style.alignSelf = alignSelf; - }, - ); + ui.PlatformViewRegistry().registerViewFactory(src, (id) { + return HTMLIFrameElement() + ..style.width = width + ..style.height = height + ..src = src + ..style.border = 'none' + ..style.margin = margin + ..style.alignSelf = alignSelf; + }); } diff --git a/flutter_quill_extensions/lib/src/common/utils/web/web_stub.dart b/flutter_quill_extensions/lib/src/common/utils/web/web_stub.dart index dac6a97be..322b0b9ae 100644 --- a/flutter_quill_extensions/lib/src/common/utils/web/web_stub.dart +++ b/flutter_quill_extensions/lib/src/common/utils/web/web_stub.dart @@ -4,9 +4,9 @@ void createHtmlImageElement({ required String width, required String margin, required String alignSelf, -}) => - throw UnimplementedError( - 'A stub method is called, createHtmlImageElement is for web platforms only.'); +}) => throw UnimplementedError( + 'A stub method is called, createHtmlImageElement is for web platforms only.', +); void createHtmlIFrameElement({ required String src, @@ -14,6 +14,6 @@ void createHtmlIFrameElement({ required String width, required String margin, required String alignSelf, -}) => - throw UnimplementedError( - 'A stub method is called, createHtmlIFrameElement is for web platforms only.'); +}) => throw UnimplementedError( + 'A stub method is called, createHtmlIFrameElement is for web platforms only.', +); diff --git a/flutter_quill_extensions/lib/src/editor/image/config/image_config.dart b/flutter_quill_extensions/lib/src/editor/image/config/image_config.dart index 3b8f1c114..35d6be9f9 100644 --- a/flutter_quill_extensions/lib/src/editor/image/config/image_config.dart +++ b/flutter_quill_extensions/lib/src/editor/image/config/image_config.dart @@ -12,12 +12,12 @@ import '../image_embed_types.dart'; @immutable class QuillEditorImageEmbedConfig { const QuillEditorImageEmbedConfig({ - ImageEmbedBuilderOnRemovedCallback? onImageRemovedCallback, + this._onImageRemovedCallback, this.shouldRemoveImageCallback, this.imageProviderBuilder, this.imageErrorWidgetBuilder, this.onImageClicked, - }) : _onImageRemovedCallback = onImageRemovedCallback; + }); /// [onImageRemovedCallback] is called when an image is /// removed from the editor. diff --git a/flutter_quill_extensions/lib/src/editor/image/config/image_web_config.dart b/flutter_quill_extensions/lib/src/editor/image/config/image_web_config.dart index b1b60b060..5424c70c8 100644 --- a/flutter_quill_extensions/lib/src/editor/image/config/image_web_config.dart +++ b/flutter_quill_extensions/lib/src/editor/image/config/image_web_config.dart @@ -3,9 +3,7 @@ import 'package:meta/meta.dart' show immutable; @immutable class QuillEditorWebImageEmbedConfig { - const QuillEditorWebImageEmbedConfig({ - this.constraints, - }); + const QuillEditorWebImageEmbedConfig({this.constraints}); final BoxConstraints? constraints; } diff --git a/flutter_quill_extensions/lib/src/editor/image/image_embed.dart b/flutter_quill_extensions/lib/src/editor/image/image_embed.dart index eb2c49730..d558bf28b 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_embed.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_embed.dart @@ -7,9 +7,7 @@ import 'image_menu.dart'; import 'widgets/image.dart'; class QuillEditorImageEmbedBuilder extends EmbedBuilder { - QuillEditorImageEmbedBuilder({ - required this.config, - }); + QuillEditorImageEmbedBuilder({required this.config}); final QuillEditorImageEmbedConfig config; @override @@ -19,10 +17,7 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { bool get expanded => false; @override - Widget build( - BuildContext context, - EmbedContext embedContext, - ) { + Widget build(BuildContext context, EmbedContext embedContext) { final imageSource = standardizeImageUrl(embedContext.node.value.data); final ((imageSize), margin, alignment) = getElementAttributes( embedContext.node, @@ -64,10 +59,7 @@ class QuillEditorImageEmbedBuilder extends EmbedBuilder { child: Builder( builder: (context) { if (margin != null) { - return Padding( - padding: EdgeInsets.all(margin), - child: imageWidget, - ); + return Padding(padding: EdgeInsets.all(margin), child: imageWidget); } return imageWidget; }, diff --git a/flutter_quill_extensions/lib/src/editor/image/image_embed_types.dart b/flutter_quill_extensions/lib/src/editor/image/image_embed_types.dart index 0ca636929..18fc0e6d3 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_embed_types.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_embed_types.dart @@ -11,28 +11,18 @@ import 'package:meta/meta.dart' show immutable; /// /// by default we already have a default implementation that show a dialog /// request the source for picking the image, from gallery, link or camera -typedef OnRequestPickImage = Future Function( - BuildContext context, -); +typedef OnRequestPickImage = Future Function(BuildContext context); /// A callback will called when inserting a image in the editor /// it have the logic that will insert the image block using the controller -typedef OnImageInsertCallback = Future Function( - String image, - QuillController controller, -); +typedef OnImageInsertCallback = + Future Function(String image, QuillController controller); /// When a new image picked this callback will called and you might want to /// do some logic depending on your use case -typedef OnImageInsertedCallback = Future Function( - String image, -); +typedef OnImageInsertedCallback = Future Function(String image); -enum InsertImageSource { - gallery, - camera, - link, -} +enum InsertImageSource { gallery, camera, link } /// Configurations for dealing with images, on insert a image /// on request picking a image @@ -51,17 +41,13 @@ class QuillToolbarImageConfig { final OnImageInsertCallback? onImageInsertCallback; } -typedef ImageEmbedBuilderWillRemoveCallback = Future Function( - String imageUrl, -); +typedef ImageEmbedBuilderWillRemoveCallback = + Future Function(String imageUrl); -typedef ImageEmbedBuilderOnRemovedCallback = Future Function( - String imageUrl, -); +typedef ImageEmbedBuilderOnRemovedCallback = + Future Function(String imageUrl); -typedef ImageEmbedBuilderProviderBuilder = ImageProvider? Function( - BuildContext context, - String imageUrl, -); +typedef ImageEmbedBuilderProviderBuilder = + ImageProvider? Function(BuildContext context, String imageUrl); typedef ImageEmbedBuilderErrorWidgetBuilder = ImageErrorWidgetBuilder; diff --git a/flutter_quill_extensions/lib/src/editor/image/image_load_utils.dart b/flutter_quill_extensions/lib/src/editor/image/image_load_utils.dart index a30af954e..6f1e22aa7 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_load_utils.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_load_utils.dart @@ -14,7 +14,7 @@ class ImageLoader { static set instance(ImageLoader newInstance) => _instance = newInstance; // TODO(performance): This will load the image again. In case -// this is a network image, then this will be inefficient. + // this is a network image, then this will be inefficient. Future loadImageBytesFromImageProvider({ required ImageProvider imageProvider, }) async { diff --git a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart index 9bb40fe90..ffbc876ec 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_menu.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_menu.dart @@ -79,11 +79,7 @@ class ImageOptionsMenu extends StatelessWidget { ); controller ..skipRequestKeyboard = true - ..formatText( - res.offset, - 1, - StyleAttribute(attr), - ); + ..formatText(res.offset, 1, StyleAttribute(attr)); }, imageWidth: imageSize.width, imageHeight: imageSize.height, @@ -106,7 +102,8 @@ class ImageOptionsMenu extends StatelessWidget { final imageBytes = await ImageLoader.instance .loadImageBytesFromImageProvider( - imageProvider: imageProvider); + imageProvider: imageProvider, + ); if (imageBytes != null) { await ClipboardServiceProvider.instance.copyImage(imageBytes); } @@ -158,35 +155,40 @@ class ImageOptionsMenu extends StatelessWidget { prefersGallerySave: prefersGallerySave, ); } on GalleryImageSaveAccessDeniedException { - messenger.showSnackBar(SnackBar( - content: Text( - localizations.saveImagePermissionDenied, - ))); + messenger.showSnackBar( + SnackBar( + content: Text(localizations.saveImagePermissionDenied), + ), + ); return; } if (result == null) { - messenger.showSnackBar(SnackBar( - content: Text( - localizations.errorUnexpectedSavingImage, - ))); + messenger.showSnackBar( + SnackBar( + content: Text(localizations.errorUnexpectedSavingImage), + ), + ); return; } if (kIsWeb) { - messenger.showSnackBar(SnackBar( - content: Text(localizations.successImageDownloaded))); + messenger.showSnackBar( + SnackBar(content: Text(localizations.successImageDownloaded)), + ); return; } if (result.isGallerySave) { - messenger.showSnackBar(SnackBar( - content: Text(localizations.successImageSavedGallery), - action: SnackBarAction( - label: localizations.openGallery, - onPressed: QuillNativeProvider.instance.openGalleryApp, + messenger.showSnackBar( + SnackBar( + content: Text(localizations.successImageSavedGallery), + action: SnackBarAction( + label: localizations.openGallery, + onPressed: QuillNativeProvider.instance.openGalleryApp, + ), ), - )); + ); return; } @@ -213,7 +215,8 @@ class ImageOptionsMenu extends StatelessWidget { : SnackBarAction( label: localizations.openFileLocation, onPressed: () => launchUrl( - Uri.directory(p.dirname(imageFilePath))), + Uri.directory(p.dirname(imageFilePath)), + ), ), ), ); @@ -222,7 +225,8 @@ class ImageOptionsMenu extends StatelessWidget { } throw StateError( - 'Image save result is not handled on $defaultTargetPlatform'); + 'Image save result is not handled on $defaultTargetPlatform', + ); }, ), ListTile( @@ -231,10 +235,8 @@ class ImageOptionsMenu extends StatelessWidget { onTap: () => Navigator.pushReplacement( context, MaterialPageRoute( - builder: (_) => ImageTapWrapper( - imageUrl: imageSource, - config: config, - ), + builder: (_) => + ImageTapWrapper(imageUrl: imageSource, config: config), ), ), ), diff --git a/flutter_quill_extensions/lib/src/editor/image/image_save_utils.dart b/flutter_quill_extensions/lib/src/editor/image/image_save_utils.dart index 7024d93d9..a14a8d1a6 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_save_utils.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_save_utils.dart @@ -95,13 +95,15 @@ String getDefaultImageFileName({required bool isGallerySave}) { } Future shouldSaveToGallery({required bool prefersGallerySave}) async { - final supportsGallerySave = await QuillNativeProvider.instance - .isSupported(QuillNativeBridgeFeature.saveImageToGallery); + final supportsGallerySave = await QuillNativeProvider.instance.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ); if (!supportsGallerySave) { return false; } - final supportsImageSave = await QuillNativeProvider.instance - .isSupported(QuillNativeBridgeFeature.saveImage); + final supportsImageSave = await QuillNativeProvider.instance.isSupported( + QuillNativeBridgeFeature.saveImage, + ); if (!supportsImageSave) { return true; } @@ -164,8 +166,9 @@ class ImageSaver { return true; }()); - final imageFileExtension = - extractImageFileExtensionFromImageSource(imageUrl); + final imageFileExtension = extractImageFileExtensionFromImageSource( + imageUrl, + ); final imageName = extractImageNameFromImageSource(imageUrl); final imageBytes = await ImageLoader.instance @@ -178,13 +181,11 @@ class ImageSaver { await QuillNativeProvider.instance.saveImage( imageBytes, options: ImageSaveOptions( - name: imageName ?? getDefaultImageFileName(isGallerySave: false), - fileExtension: imageFileExtension), - ); - return const SaveImageResult( - imageFilePath: null, - isGallerySave: false, + name: imageName ?? getDefaultImageFileName(isGallerySave: false), + fileExtension: imageFileExtension, + ), ); + return const SaveImageResult(imageFilePath: null, isGallerySave: false); } if (await shouldSaveToGallery(prefersGallerySave: prefersGallerySave)) { @@ -201,10 +202,7 @@ class ImageSaver { ), ); - return const SaveImageResult( - imageFilePath: null, - isGallerySave: true, - ); + return const SaveImageResult(imageFilePath: null, isGallerySave: true); } on PlatformException catch (e) { // TODO(save-image): Part of https://github.com/FlutterQuill/quill-native-bridge/issues/2 @@ -230,10 +228,13 @@ class ImageSaver { } } - if (await QuillNativeProvider.instance - .isSupported(QuillNativeBridgeFeature.saveImage)) { - assert(!isMobileApp, - 'Mobile platforms support saving images to the gallery only'); + if (await QuillNativeProvider.instance.isSupported( + QuillNativeBridgeFeature.saveImage, + )) { + assert( + !isMobileApp, + 'Mobile platforms support saving images to the gallery only', + ); final result = await QuillNativeProvider.instance.saveImage( imageBytes, diff --git a/flutter_quill_extensions/lib/src/editor/image/image_web_embed.dart b/flutter_quill_extensions/lib/src/editor/image/image_web_embed.dart index 1b2102265..05a556333 100644 --- a/flutter_quill_extensions/lib/src/editor/image/image_web_embed.dart +++ b/flutter_quill_extensions/lib/src/editor/image/image_web_embed.dart @@ -8,9 +8,7 @@ import '../../common/utils/web/web.dart'; import 'config/image_web_config.dart'; class QuillEditorWebImageEmbedBuilder extends EmbedBuilder { - const QuillEditorWebImageEmbedBuilder({ - required this.config, - }); + const QuillEditorWebImageEmbedBuilder({required this.config}); final QuillEditorWebImageEmbedConfig config; @@ -21,14 +19,12 @@ class QuillEditorWebImageEmbedBuilder extends EmbedBuilder { bool get expanded => false; @override - Widget build( - BuildContext context, - EmbedContext embedContext, - ) { + Widget build(BuildContext context, EmbedContext embedContext) { assert(kIsWeb, 'ImageEmbedBuilderWeb is only for web platform'); - final (height, width, margin, alignment) = - getWebElementAttributes(embedContext.node); + final (height, width, margin, alignment) = getWebElementAttributes( + embedContext.node, + ); var imageSource = embedContext.node.value.data.toString(); @@ -56,9 +52,7 @@ class QuillEditorWebImageEmbedBuilder extends EmbedBuilder { return ConstrainedBox( constraints: config.constraints ?? BoxConstraints.loose(const Size(200, 200)), - child: HtmlElementView( - viewType: imageSource, - ), + child: HtmlElementView(viewType: imageSource), ); } } diff --git a/flutter_quill_extensions/lib/src/editor/image/widgets/image.dart b/flutter_quill_extensions/lib/src/editor/image/widgets/image.dart index a1b0dc0e8..760203328 100644 --- a/flutter_quill_extensions/lib/src/editor/image/widgets/image.dart +++ b/flutter_quill_extensions/lib/src/editor/image/widgets/image.dart @@ -13,8 +13,10 @@ import '../image_embed_types.dart'; String getImageStyleString(QuillController controller) { final String? s = controller .getAllSelectionStyles() - .firstWhere((s) => s.attributes.containsKey(Attribute.style.key), - orElse: Style.new) + .firstWhere( + (s) => s.attributes.containsKey(Attribute.style.key), + orElse: Style.new, + ) .attributes[Attribute.style.key] ?.value; return s ?? ''; @@ -86,7 +88,7 @@ const List _imageFileExtensions = [ '.gif', '.webp', '.tif', - '.heic' + '.heic', ]; /// This is a bug of Gallery Saver Package. @@ -96,14 +98,18 @@ const List _imageFileExtensions = [ /// If imageUrl does not end with it's file extension, /// file extension is added to image url for saving. String appendFileExtensionToImageUrl(String url) { - final endsWithImageFileExtension = _imageFileExtensions - .firstWhere((s) => url.toLowerCase().endsWith(s), orElse: () => ''); + final endsWithImageFileExtension = _imageFileExtensions.firstWhere( + (s) => url.toLowerCase().endsWith(s), + orElse: () => '', + ); if (endsWithImageFileExtension.isNotEmpty) { return url; } - final imageFileExtension = _imageFileExtensions - .firstWhere((s) => url.toLowerCase().contains(s), orElse: () => ''); + final imageFileExtension = _imageFileExtensions.firstWhere( + (s) => url.toLowerCase().contains(s), + orElse: () => '', + ); return url + imageFileExtension; } @@ -137,9 +143,7 @@ class ImageTapWrapper extends StatelessWidget { loadingBuilder: (context, event) { return Container( color: Colors.black, - child: const Center( - child: CircularProgressIndicator(), - ), + child: const Center(child: CircularProgressIndicator()), ); }, ), @@ -173,7 +177,7 @@ class ImageTapWrapper extends StatelessWidget { color: Colors.grey[400], size: 28, ), - ) + ), ], ), ), diff --git a/flutter_quill_extensions/lib/src/editor/image/widgets/image_resizer.dart b/flutter_quill_extensions/lib/src/editor/image/widgets/image_resizer.dart index 5f481f520..959c19ce0 100644 --- a/flutter_quill_extensions/lib/src/editor/image/widgets/image_resizer.dart +++ b/flutter_quill_extensions/lib/src/editor/image/widgets/image_resizer.dart @@ -46,24 +46,15 @@ class ImageResizerState extends State { Widget _showMaterialMenu() { return Column( mainAxisSize: MainAxisSize.min, - children: [ - _widthSlider(), - _heightSlider(), - ], + children: [_widthSlider(), _heightSlider()], ); } Widget _showCupertinoMenu() { return CupertinoActionSheet( actions: [ - CupertinoActionSheetAction( - onPressed: () {}, - child: _widthSlider(), - ), - CupertinoActionSheetAction( - onPressed: () {}, - child: _heightSlider(), - ) + CupertinoActionSheetAction(onPressed: () {}, child: _widthSlider()), + CupertinoActionSheetAction(onPressed: () {}, child: _heightSlider()), ], ); } diff --git a/flutter_quill_extensions/lib/src/editor/video/video_embed.dart b/flutter_quill_extensions/lib/src/editor/video/video_embed.dart index bc55b2344..89476edcb 100644 --- a/flutter_quill_extensions/lib/src/editor/video/video_embed.dart +++ b/flutter_quill_extensions/lib/src/editor/video/video_embed.dart @@ -6,9 +6,7 @@ import 'config/video_config.dart'; import 'widgets/video_app.dart'; class QuillEditorVideoEmbedBuilder extends EmbedBuilder { - const QuillEditorVideoEmbedBuilder({ - required this.config, - }); + const QuillEditorVideoEmbedBuilder({required this.config}); final QuillEditorVideoEmbedConfig config; @@ -19,10 +17,7 @@ class QuillEditorVideoEmbedBuilder extends EmbedBuilder { bool get expanded => false; @override - Widget build( - BuildContext context, - EmbedContext embedContext, - ) { + Widget build(BuildContext context, EmbedContext embedContext) { final videoUrl = embedContext.node.value.data; final customVideoBuilder = config.customVideoBuilder; diff --git a/flutter_quill_extensions/lib/src/editor/video/video_web_embed.dart b/flutter_quill_extensions/lib/src/editor/video/video_web_embed.dart index f38ceae88..c9814c22d 100644 --- a/flutter_quill_extensions/lib/src/editor/video/video_web_embed.dart +++ b/flutter_quill_extensions/lib/src/editor/video/video_web_embed.dart @@ -8,9 +8,7 @@ import 'config/video_web_config.dart'; import 'youtube_video_url.dart'; class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder { - const QuillEditorWebVideoEmbedBuilder({ - required this.config, - }); + const QuillEditorWebVideoEmbedBuilder({required this.config}); final QuillEditorWebVideoEmbedConfig config; @@ -21,10 +19,7 @@ class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder { bool get expanded => false; @override - Widget build( - BuildContext context, - EmbedContext embedContext, - ) { + Widget build(BuildContext context, EmbedContext embedContext) { var videoUrl = embedContext.node.value.data; if (isYouTubeUrl(videoUrl)) { // ignore: deprecated_member_use_from_same_package @@ -34,8 +29,9 @@ class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder { } } - final (height, width, margin, alignment) = - getWebElementAttributes(embedContext.node); + final (height, width, margin, alignment) = getWebElementAttributes( + embedContext.node, + ); createHtmlIFrameElement( src: videoUrl, @@ -45,11 +41,6 @@ class QuillEditorWebVideoEmbedBuilder extends EmbedBuilder { alignSelf: alignment, ); - return SizedBox( - height: 500, - child: HtmlElementView( - viewType: videoUrl, - ), - ); + return SizedBox(height: 500, child: HtmlElementView(viewType: videoUrl)); } } diff --git a/flutter_quill_extensions/lib/src/editor/video/widgets/video_app.dart b/flutter_quill_extensions/lib/src/editor/video/widgets/video_app.dart index 6e86cb43e..4440afbd4 100644 --- a/flutter_quill_extensions/lib/src/editor/video/widgets/video_app.dart +++ b/flutter_quill_extensions/lib/src/editor/video/widgets/video_app.dart @@ -34,19 +34,22 @@ class VideoAppState extends State { void initState() { super.initState(); - _controller = isHttpUrl(widget.videoUrl) - ? VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)) - : VideoPlayerController.file(File(widget.videoUrl)) - ..initialize().then((_) { - // Ensure the first frame is shown after the video is initialized, - // even before the play button has been pressed. - setState(() {}); - if (widget.onVideoInit != null) { - widget.onVideoInit?.call(videoContainerKey); - } - }).catchError((error) { - setState(() {}); - }); + _controller = + isHttpUrl(widget.videoUrl) + ? VideoPlayerController.networkUrl(Uri.parse(widget.videoUrl)) + : VideoPlayerController.file(File(widget.videoUrl)) + ..initialize() + .then((_) { + // Ensure the first frame is shown after the video is initialized, + // even before the play button has been pressed. + setState(() {}); + if (widget.onVideoInit != null) { + widget.onVideoInit?.call(videoContainerKey); + } + }) + .catchError((error) { + setState(() {}); + }); } @override @@ -59,18 +62,13 @@ class VideoAppState extends State { text: widget.videoUrl, style: defaultStyles.link, recognizer: TapGestureRecognizer() - ..onTap = () => launchUrl( - Uri.parse(widget.videoUrl), - ), + ..onTap = () => launchUrl(Uri.parse(widget.videoUrl)), ), ); } return RichText( - text: TextSpan( - text: widget.videoUrl, - style: defaultStyles.link, - ), + text: TextSpan(text: widget.videoUrl, style: defaultStyles.link), ); } else if (!_controller.value.isInitialized) { return VideoProgressIndicator( @@ -94,10 +92,11 @@ class VideoAppState extends State { alignment: Alignment.center, children: [ Center( - child: AspectRatio( - aspectRatio: _controller.value.aspectRatio, - child: VideoPlayer(_controller), - )), + child: AspectRatio( + aspectRatio: _controller.value.aspectRatio, + child: VideoPlayer(_controller), + ), + ), _controller.value.isPlaying ? const SizedBox.shrink() : Container( @@ -107,7 +106,7 @@ class VideoAppState extends State { size: 60, color: Colors.blueGrey, ), - ) + ), ], ), ), diff --git a/flutter_quill_extensions/lib/src/editor/video/youtube_video_url.dart b/flutter_quill_extensions/lib/src/editor/video/youtube_video_url.dart index 379f9fdb0..aaa34489c 100644 --- a/flutter_quill_extensions/lib/src/editor/video/youtube_video_url.dart +++ b/flutter_quill_extensions/lib/src/editor/video/youtube_video_url.dart @@ -15,14 +15,18 @@ String? convertVideoUrlToId(String url, {bool trimWhitespaces = true}) { for (final exp in [ RegExp( - r'^https:\/\/(?:www\.|m\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$'), + r'^https:\/\/(?:www\.|m\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$', + ), RegExp( - r'^https:\/\/(?:music\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$'), + r'^https:\/\/(?:music\.)?youtube\.com\/watch\?v=([_\-a-zA-Z0-9]{11}).*$', + ), RegExp( - r'^https:\/\/(?:www\.|m\.)?youtube\.com\/shorts\/([_\-a-zA-Z0-9]{11}).*$'), + r'^https:\/\/(?:www\.|m\.)?youtube\.com\/shorts\/([_\-a-zA-Z0-9]{11}).*$', + ), RegExp( - r'^https:\/\/(?:www\.|m\.)?youtube(?:-nocookie)?\.com\/embed\/([_\-a-zA-Z0-9]{11}).*$'), - RegExp(r'^https:\/\/youtu\.be\/([_\-a-zA-Z0-9]{11}).*$') + r'^https:\/\/(?:www\.|m\.)?youtube(?:-nocookie)?\.com\/embed\/([_\-a-zA-Z0-9]{11}).*$', + ), + RegExp(r'^https:\/\/youtu\.be\/([_\-a-zA-Z0-9]{11}).*$'), ]) { final Match? match = exp.firstMatch(url); if (match != null && match.groupCount >= 1) return match.group(1); diff --git a/flutter_quill_extensions/lib/src/flutter_quill_embeds.dart b/flutter_quill_extensions/lib/src/flutter_quill_embeds.dart index bd46aa762..2cec066f3 100644 --- a/flutter_quill_extensions/lib/src/flutter_quill_embeds.dart +++ b/flutter_quill_extensions/lib/src/flutter_quill_embeds.dart @@ -26,13 +26,9 @@ abstract final class FlutterQuillEmbeds { }) { return [ if (imageEmbedConfig != null) - QuillEditorImageEmbedBuilder( - config: imageEmbedConfig, - ), + QuillEditorImageEmbedBuilder(config: imageEmbedConfig), if (videoEmbedConfig != null) - QuillEditorVideoEmbedBuilder( - config: videoEmbedConfig, - ), + QuillEditorVideoEmbedBuilder(config: videoEmbedConfig), ]; } @@ -53,13 +49,9 @@ abstract final class FlutterQuillEmbeds { } return [ if (imageEmbedConfig != null) - QuillEditorImageEmbedBuilder( - config: imageEmbedConfig, - ), + QuillEditorImageEmbedBuilder(config: imageEmbedConfig), if (videoEmbedConfig != null) - QuillEditorWebVideoEmbedBuilder( - config: videoEmbedConfig, - ), + QuillEditorWebVideoEmbedBuilder(config: videoEmbedConfig), ]; } @@ -79,28 +71,27 @@ abstract final class FlutterQuillEmbeds { QuillToolbarVideoButtonOptions? videoButtonOptions = const QuillToolbarVideoButtonOptions(), QuillToolbarCameraButtonOptions? cameraButtonOptions, - }) => - [ - if (imageButtonOptions != null) - (context, embedContext) => QuillToolbarImageButton( - controller: embedContext.controller, - options: imageButtonOptions, - // ignore: invalid_use_of_internal_member - baseOptions: embedContext.baseButtonOptions, - ), - if (videoButtonOptions != null) - (context, embedContext) => QuillToolbarVideoButton( - controller: embedContext.controller, - options: videoButtonOptions, - // ignore: invalid_use_of_internal_member - baseOptions: embedContext.baseButtonOptions, - ), - if (cameraButtonOptions != null) - (context, embedContext) => QuillToolbarCameraButton( - controller: embedContext.controller, - options: cameraButtonOptions, - // ignore: invalid_use_of_internal_member - baseOptions: embedContext.baseButtonOptions, - ), - ]; + }) => [ + if (imageButtonOptions != null) + (context, embedContext) => QuillToolbarImageButton( + controller: embedContext.controller, + options: imageButtonOptions, + // ignore: invalid_use_of_internal_member + baseOptions: embedContext.baseButtonOptions, + ), + if (videoButtonOptions != null) + (context, embedContext) => QuillToolbarVideoButton( + controller: embedContext.controller, + options: videoButtonOptions, + // ignore: invalid_use_of_internal_member + baseOptions: embedContext.baseButtonOptions, + ), + if (cameraButtonOptions != null) + (context, embedContext) => QuillToolbarCameraButton( + controller: embedContext.controller, + options: cameraButtonOptions, + // ignore: invalid_use_of_internal_member + baseOptions: embedContext.baseButtonOptions, + ), + ]; } diff --git a/flutter_quill_extensions/lib/src/toolbar/camera/camera_button.dart b/flutter_quill_extensions/lib/src/toolbar/camera/camera_button.dart index e9b18abdb..143d0fdee 100644 --- a/flutter_quill_extensions/lib/src/toolbar/camera/camera_button.dart +++ b/flutter_quill_extensions/lib/src/toolbar/camera/camera_button.dart @@ -20,8 +20,8 @@ class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless { /// over the [baseOptions]. super.baseOptions, super.key, - }) : _options = options, - super(options: options); + }) : _options = options, + super(options: options); final QuillToolbarCameraButtonOptions? _options; @@ -29,10 +29,7 @@ class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless { QuillToolbarCameraButtonOptions? get options => _options; void _sharedOnPressed(BuildContext context) { - _onPressedHandler( - context, - controller, - ); + _onPressedHandler(context, controller); afterButtonPressed(context); } @@ -41,9 +38,7 @@ class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless { if (customCallback != null) { return await customCallback(context); } - final cameraAction = await showSelectCameraActionDialog( - context: context, - ); + final cameraAction = await showSelectCameraActionDialog(context: context); return cameraAction; } @@ -60,8 +55,9 @@ class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless { switch (cameraAction) { case CameraAction.video: - final videoFile = - await ImagePicker().pickVideo(source: ImageSource.camera); + final videoFile = await ImagePicker().pickVideo( + source: ImageSource.camera, + ); if (videoFile == null) { return; } @@ -73,8 +69,9 @@ class QuillToolbarCameraButton extends QuillToolbarBaseButtonStateless { options?.cameraConfig?.onVideoInsertedCallback, ); case CameraAction.image: - final imageFile = - await ImagePicker().pickImage(source: ImageSource.camera); + final imageFile = await ImagePicker().pickImage( + source: ImageSource.camera, + ); if (imageFile == null) { return; } diff --git a/flutter_quill_extensions/lib/src/toolbar/camera/camera_types.dart b/flutter_quill_extensions/lib/src/toolbar/camera/camera_types.dart index 04822fd68..357dec149 100644 --- a/flutter_quill_extensions/lib/src/toolbar/camera/camera_types.dart +++ b/flutter_quill_extensions/lib/src/toolbar/camera/camera_types.dart @@ -4,18 +4,14 @@ import 'package:meta/meta.dart' show immutable; import '../../editor/image/image_embed_types.dart'; import '../video/config/video.dart'; -enum CameraAction { - video, - image, -} +enum CameraAction { video, image } /// When the user click the camera button, should we take a photo or record /// a video using the camera /// /// by default will show a dialog that ask the user which option he/she wants -typedef OnRequestCameraActionCallback = Future Function( - BuildContext context, -); +typedef OnRequestCameraActionCallback = + Future Function(BuildContext context); @immutable class QuillToolbarCameraConfig { diff --git a/flutter_quill_extensions/lib/src/toolbar/camera/config/camera_config.dart b/flutter_quill_extensions/lib/src/toolbar/camera/config/camera_config.dart index e4984464b..d04e5c213 100644 --- a/flutter_quill_extensions/lib/src/toolbar/camera/config/camera_config.dart +++ b/flutter_quill_extensions/lib/src/toolbar/camera/config/camera_config.dart @@ -11,8 +11,12 @@ class QuillToolbarCameraButtonExtraOptions }); } -class QuillToolbarCameraButtonOptions extends QuillToolbarBaseButtonOptions< - QuillToolbarCameraButtonOptions, QuillToolbarCameraButtonExtraOptions> { +class QuillToolbarCameraButtonOptions + extends + QuillToolbarBaseButtonOptions< + QuillToolbarCameraButtonOptions, + QuillToolbarCameraButtonExtraOptions + > { const QuillToolbarCameraButtonOptions({ this.cameraConfig, super.iconSize, diff --git a/flutter_quill_extensions/lib/src/toolbar/camera/select_camera_action.dart b/flutter_quill_extensions/lib/src/toolbar/camera/select_camera_action.dart index 2c0c3f84b..63d4bd79f 100644 --- a/flutter_quill_extensions/lib/src/toolbar/camera/select_camera_action.dart +++ b/flutter_quill_extensions/lib/src/toolbar/camera/select_camera_action.dart @@ -16,18 +16,14 @@ class SelectCameraActionDialog extends StatelessWidget { children: [ ListTile( title: Text(context.loc.photo), - subtitle: Text( - context.loc.takeAPhotoUsingYourCamera, - ), + subtitle: Text(context.loc.takeAPhotoUsingYourCamera), leading: const Icon(Icons.photo_sharp), enabled: !isDesktopApp, onTap: () => Navigator.of(context).pop(CameraAction.image), ), ListTile( title: Text(context.loc.video), - subtitle: Text( - context.loc.recordAVideoUsingYourCamera, - ), + subtitle: Text(context.loc.recordAVideoUsingYourCamera), leading: const Icon(Icons.camera), enabled: !isDesktopApp, onTap: () => Navigator.of(context).pop(CameraAction.video), diff --git a/flutter_quill_extensions/lib/src/toolbar/image/config/image_config.dart b/flutter_quill_extensions/lib/src/toolbar/image/config/image_config.dart index 06b3103ff..98a3fd2dc 100644 --- a/flutter_quill_extensions/lib/src/toolbar/image/config/image_config.dart +++ b/flutter_quill_extensions/lib/src/toolbar/image/config/image_config.dart @@ -13,8 +13,12 @@ class QuillToolbarImageButtonExtraOptions } @immutable -class QuillToolbarImageButtonOptions extends QuillToolbarBaseButtonOptions< - QuillToolbarImageButtonOptions, QuillToolbarImageButtonExtraOptions> { +class QuillToolbarImageButtonOptions + extends + QuillToolbarBaseButtonOptions< + QuillToolbarImageButtonOptions, + QuillToolbarImageButtonExtraOptions + > { const QuillToolbarImageButtonOptions({ super.iconData, super.iconSize, diff --git a/flutter_quill_extensions/lib/src/toolbar/image/image_button.dart b/flutter_quill_extensions/lib/src/toolbar/image/image_button.dart index 65a651090..b6769a8d8 100644 --- a/flutter_quill_extensions/lib/src/toolbar/image/image_button.dart +++ b/flutter_quill_extensions/lib/src/toolbar/image/image_button.dart @@ -20,8 +20,8 @@ class QuillToolbarImageButton extends QuillToolbarBaseButtonStateless { /// over the [baseOptions]. super.baseOptions, super.key, - }) : _options = options, - super(options: options); + }) : _options = options, + super(options: options); final QuillToolbarImageButtonOptions? _options; @@ -46,28 +46,26 @@ class QuillToolbarImageButton extends QuillToolbarBaseButtonStateless { Future _onPressedHandler(BuildContext context) async { final onRequestPickImage = options?.imageButtonConfig?.onRequestPickImage; if (onRequestPickImage != null) { - final imageUrl = await onRequestPickImage( - context, - ); + final imageUrl = await onRequestPickImage(context); if (imageUrl != null) { await _handleImageInsert(imageUrl); } return; } - final source = await showSelectImageSourceDialog( - context: context, - ); + final source = await showSelectImageSourceDialog(context: context); if (source == null) { return; } final imageUrl = switch (source) { - InsertImageSource.gallery => - (await ImagePicker().pickImage(source: ImageSource.gallery))?.path, + InsertImageSource.gallery => (await ImagePicker().pickImage( + source: ImageSource.gallery, + ))?.path, InsertImageSource.link => context.mounted ? await _typeLink(context) : null, - InsertImageSource.camera => - (await ImagePicker().pickImage(source: ImageSource.camera))?.path, + InsertImageSource.camera => (await ImagePicker().pickImage( + source: ImageSource.camera, + ))?.path, }; if (imageUrl == null) { return; diff --git a/flutter_quill_extensions/lib/src/toolbar/image/select_image_source.dart b/flutter_quill_extensions/lib/src/toolbar/image/select_image_source.dart index 80f846a28..d45d5950a 100644 --- a/flutter_quill_extensions/lib/src/toolbar/image/select_image_source.dart +++ b/flutter_quill_extensions/lib/src/toolbar/image/select_image_source.dart @@ -16,26 +16,20 @@ class SelectImageSourceDialog extends StatelessWidget { children: [ ListTile( title: Text(context.loc.gallery), - subtitle: Text( - context.loc.pickAPhotoFromYourGallery, - ), + subtitle: Text(context.loc.pickAPhotoFromYourGallery), leading: const Icon(Icons.photo_sharp), onTap: () => Navigator.of(context).pop(InsertImageSource.gallery), ), ListTile( title: Text(context.loc.camera), - subtitle: Text( - context.loc.takeAPhotoUsingYourCamera, - ), + subtitle: Text(context.loc.takeAPhotoUsingYourCamera), leading: const Icon(Icons.camera), enabled: !isDesktopApp, onTap: () => Navigator.of(context).pop(InsertImageSource.camera), ), ListTile( title: Text(context.loc.link), - subtitle: Text( - context.loc.pasteAPhotoUsingALink, - ), + subtitle: Text(context.loc.pasteAPhotoUsingALink), leading: const Icon(Icons.link), onTap: () => Navigator.of(context).pop(InsertImageSource.link), ), diff --git a/flutter_quill_extensions/lib/src/toolbar/video/config/video.dart b/flutter_quill_extensions/lib/src/toolbar/video/config/video.dart index 0a3c2bf69..42aff16c1 100644 --- a/flutter_quill_extensions/lib/src/toolbar/video/config/video.dart +++ b/flutter_quill_extensions/lib/src/toolbar/video/config/video.dart @@ -9,28 +9,18 @@ import 'package:meta/meta.dart' show immutable; /// /// by default we already have a default implementation that show a dialog /// request the source for picking the video, from gallery, link or camera -typedef OnRequestPickVideo = Future Function( - BuildContext context, -); +typedef OnRequestPickVideo = Future Function(BuildContext context); /// A callback will called when inserting a video in the editor /// it have the logic that will insert the video block using the controller -typedef OnVideoInsertCallback = Future Function( - String video, - QuillController controller, -); +typedef OnVideoInsertCallback = + Future Function(String video, QuillController controller); /// When a new video picked this callback will called and you might want to /// do some logic depending on your use case -typedef OnVideoInsertedCallback = Future Function( - String video, -); - -enum InsertVideoSource { - gallery, - camera, - link, -} +typedef OnVideoInsertedCallback = Future Function(String video); + +enum InsertVideoSource { gallery, camera, link } /// Configurations for dealing with videos, on insert a video /// on request picking a video diff --git a/flutter_quill_extensions/lib/src/toolbar/video/config/video_config.dart b/flutter_quill_extensions/lib/src/toolbar/video/config/video_config.dart index 268a4bde4..d5385fa9e 100644 --- a/flutter_quill_extensions/lib/src/toolbar/video/config/video_config.dart +++ b/flutter_quill_extensions/lib/src/toolbar/video/config/video_config.dart @@ -11,8 +11,12 @@ class QuillToolbarVideoButtonExtraOptions }); } -class QuillToolbarVideoButtonOptions extends QuillToolbarBaseButtonOptions< - QuillToolbarVideoButtonOptions, QuillToolbarVideoButtonExtraOptions> { +class QuillToolbarVideoButtonOptions + extends + QuillToolbarBaseButtonOptions< + QuillToolbarVideoButtonOptions, + QuillToolbarVideoButtonExtraOptions + > { const QuillToolbarVideoButtonOptions({ this.linkRegExp, this.dialogTheme, diff --git a/flutter_quill_extensions/lib/src/toolbar/video/select_video_source.dart b/flutter_quill_extensions/lib/src/toolbar/video/select_video_source.dart index 4020fc806..850bca36d 100644 --- a/flutter_quill_extensions/lib/src/toolbar/video/select_video_source.dart +++ b/flutter_quill_extensions/lib/src/toolbar/video/select_video_source.dart @@ -16,9 +16,7 @@ class SelectVideoSourceDialog extends StatelessWidget { children: [ ListTile( title: Text(context.loc.gallery), - subtitle: Text( - context.loc.pickAVideoFromYourGallery, - ), + subtitle: Text(context.loc.pickAVideoFromYourGallery), leading: const Icon(Icons.photo_sharp), onTap: () => Navigator.of(context).pop(InsertVideoSource.gallery), ), @@ -31,9 +29,7 @@ class SelectVideoSourceDialog extends StatelessWidget { ), ListTile( title: Text(context.loc.link), - subtitle: Text( - context.loc.pasteAVideoUsingALink, - ), + subtitle: Text(context.loc.pasteAVideoUsingALink), leading: const Icon(Icons.link), onTap: () => Navigator.of(context).pop(InsertVideoSource.link), ), diff --git a/flutter_quill_extensions/lib/src/toolbar/video/video_button.dart b/flutter_quill_extensions/lib/src/toolbar/video/video_button.dart index 33d160b51..22d539a91 100644 --- a/flutter_quill_extensions/lib/src/toolbar/video/video_button.dart +++ b/flutter_quill_extensions/lib/src/toolbar/video/video_button.dart @@ -22,8 +22,8 @@ class QuillToolbarVideoButton extends QuillToolbarBaseButtonStateless { /// over the [baseOptions]. super.baseOptions, super.key, - }) : _options = options, - super(options: options); + }) : _options = options, + super(options: options); final QuillToolbarVideoButtonOptions? _options; @@ -61,10 +61,12 @@ class QuillToolbarVideoButton extends QuillToolbarBaseButtonStateless { } final videoUrl = switch (imageSource) { - InsertVideoSource.gallery => - (await ImagePicker().pickVideo(source: ImageSource.gallery))?.path, - InsertVideoSource.camera => - (await ImagePicker().pickVideo(source: ImageSource.camera))?.path, + InsertVideoSource.gallery => (await ImagePicker().pickVideo( + source: ImageSource.gallery, + ))?.path, + InsertVideoSource.camera => (await ImagePicker().pickVideo( + source: ImageSource.camera, + ))?.path, InsertVideoSource.link => context.mounted ? await _typeLink(context) : null, }; diff --git a/flutter_quill_extensions/pubspec.yaml b/flutter_quill_extensions/pubspec.yaml index 6f5cae329..b7c3e806d 100644 --- a/flutter_quill_extensions/pubspec.yaml +++ b/flutter_quill_extensions/pubspec.yaml @@ -16,8 +16,8 @@ platforms: windows: environment: - sdk: '>=3.0.0 <4.0.0' - flutter: ">=3.0.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" # The versions listed here indicate the minimum required for dependencies # and do not affect those used in the client's application. diff --git a/flutter_quill_extensions/test/editor/image/image_save_utils_test.dart b/flutter_quill_extensions/test/editor/image/image_save_utils_test.dart index 03d7913a2..c4764be61 100644 --- a/flutter_quill_extensions/test/editor/image/image_save_utils_test.dart +++ b/flutter_quill_extensions/test/editor/image/image_save_utils_test.dart @@ -15,39 +15,58 @@ void main() { expect(defaultImageFileExtension, equals('png')); }); - test('returns $defaultImageFileExtension when file name is null or empty', - () { - expect(extractImageFileExtensionFromImageSource(null), - equals(defaultImageFileExtension)); - expect(extractImageFileExtensionFromImageSource(''), - equals(defaultImageFileExtension)); - }); + test( + 'returns $defaultImageFileExtension when file name is null or empty', + () { + expect( + extractImageFileExtensionFromImageSource(null), + equals(defaultImageFileExtension), + ); + expect( + extractImageFileExtensionFromImageSource(''), + equals(defaultImageFileExtension), + ); + }, + ); - test('returns $defaultImageFileExtension when file name does not have dot', - () { - expect(extractImageFileExtensionFromImageSource('imagepng'), - equals(defaultImageFileExtension)); - expect(extractImageFileExtensionFromImageSource('image png'), - equals(defaultImageFileExtension)); - expect(extractImageFileExtensionFromImageSource('image'), - equals(defaultImageFileExtension)); - expect(extractImageFileExtensionFromImageSource('png'), - equals(defaultImageFileExtension)); - }); + test( + 'returns $defaultImageFileExtension when file name does not have dot', + () { + expect( + extractImageFileExtensionFromImageSource('imagepng'), + equals(defaultImageFileExtension), + ); + expect( + extractImageFileExtensionFromImageSource('image png'), + equals(defaultImageFileExtension), + ); + expect( + extractImageFileExtensionFromImageSource('image'), + equals(defaultImageFileExtension), + ); + expect( + extractImageFileExtensionFromImageSource('png'), + equals(defaultImageFileExtension), + ); + }, + ); test('returns the file extension correctly', () { for (final fileExtension in testImageExtensions) { - expect(extractImageFileExtensionFromImageSource('image.$fileExtension'), - equals(fileExtension)); + expect( + extractImageFileExtensionFromImageSource('image.$fileExtension'), + equals(fileExtension), + ); } }); }); group('extractImageNameFromImageSource', () { test( - 'returns the file name without the extension when a valid name is given', - () { - expect(extractImageNameFromImageSource('image.jpg'), 'image'); - }); + 'returns the file name without the extension when a valid name is given', + () { + expect(extractImageNameFromImageSource('image.jpg'), 'image'); + }, + ); test('returns null when the input is null or empty', () { expect(extractImageNameFromImageSource(null), isNull); @@ -57,7 +76,9 @@ void main() { test('returns the image name correctly', () { for (final fileExtension in testImageExtensions) { expect( - extractImageNameFromImageSource('image.$fileExtension'), 'image'); + extractImageNameFromImageSource('image.$fileExtension'), + 'image', + ); } }); @@ -66,7 +87,8 @@ void main() { const imageName = 'image'; expect( extractImageNameFromImageSource( - 'https://example.com/path/to/file/$imageName.png'), + 'https://example.com/path/to/file/$imageName.png', + ), equals(imageName), ); }); @@ -75,27 +97,31 @@ void main() { const imageName = 'quill_image'; expect( extractImageNameFromImageSource( - 'https://example.com/path/to/file/$imageName.png?version=1.2.3'), + 'https://example.com/path/to/file/$imageName.png?version=1.2.3', + ), equals(imageName), ); }); test( - 'extracts image name from a URL with query parameters and without extension', - () { - const imageName = 'quill_image'; - expect( - extractImageNameFromImageSource( - 'https://example.com/path/to/file/$imageName?version=1.2.3'), - equals(imageName), - ); - }); + 'extracts image name from a URL with query parameters and without extension', + () { + const imageName = 'quill_image'; + expect( + extractImageNameFromImageSource( + 'https://example.com/path/to/file/$imageName?version=1.2.3', + ), + equals(imageName), + ); + }, + ); test('extracts image name from a URL with query parameters', () { const imageName = '2019-Metrology-Events.jpg'; expect( extractImageNameFromImageSource( - 'https://firebasestorage.googleapis.com/v0/b/eventat-4ba96.appspot.com/o/$imageName.jpg?alt=media&token=bfc47032-5173-4b3f-86bb-9659f46b362a'), + 'https://firebasestorage.googleapis.com/v0/b/eventat-4ba96.appspot.com/o/$imageName.jpg?alt=media&token=bfc47032-5173-4b3f-86bb-9659f46b362a', + ), equals(imageName), ); }); @@ -118,23 +144,22 @@ void main() { const imageName = 'ExampleImage'; expect( extractImageNameFromImageSource( - 'https://example.com/path/to/extra/level/$imageName.webp'), + 'https://example.com/path/to/extra/level/$imageName.webp', + ), equals(imageName), ); }); test('returns null for URLs without any path components', () { - expect( - extractImageNameFromImageSource('https://example.com'), - isNull, - ); + expect(extractImageNameFromImageSource('https://example.com'), isNull); }); test('extracts image name from a URL with special characters', () { const imageName = '2013-report-final_v2'; expect( extractImageNameFromImageSource( - 'https://example.com/files/$imageName'), + 'https://example.com/files/$imageName', + ), equals(imageName), ); }); @@ -159,10 +184,7 @@ void main() { }); test('returns null for an empty path', () { - expect( - extractImageNameFromImageSource(''), - isNull, - ); + expect(extractImageNameFromImageSource(''), isNull); }); test('handles paths without a file name', () { @@ -182,15 +204,16 @@ void main() { }); test( - 'returns null for a path that is just a file name with no directories', - () { - const imageName = 'document'; + 'returns null for a path that is just a file name with no directories', + () { + const imageName = 'document'; - expect( - extractImageNameFromImageSource('$imageName.png'), - equals(imageName), - ); - }); + expect( + extractImageNameFromImageSource('$imageName.png'), + equals(imageName), + ); + }, + ); test('handles paths that ends with a slash', () { const imageName = 'Image'; expect( @@ -207,8 +230,9 @@ void main() { const isGallerySave = false; expect( const SaveImageResult( - imageFilePath: imageFilePath, isGallerySave: isGallerySave) - .toString(), + imageFilePath: imageFilePath, + isGallerySave: isGallerySave, + ).toString(), 'SaveImageResult(imageFilePath: $imageFilePath, isGallerySave: $isGallerySave)', ); }); @@ -219,9 +243,13 @@ void main() { expect( const SaveImageResult( - imageFilePath: imageFilePath, isGallerySave: isGallerySave), + imageFilePath: imageFilePath, + isGallerySave: isGallerySave, + ), const SaveImageResult( - imageFilePath: imageFilePath, isGallerySave: isGallerySave), + imageFilePath: imageFilePath, + isGallerySave: isGallerySave, + ), reason: 'two instances with the same values should be equal', ); }); @@ -231,11 +259,13 @@ void main() { const isGallerySave = false; expect( const SaveImageResult( - imageFilePath: imageFilePath, isGallerySave: isGallerySave) - .hashCode, + imageFilePath: imageFilePath, + isGallerySave: isGallerySave, + ).hashCode, const SaveImageResult( - imageFilePath: imageFilePath, isGallerySave: isGallerySave) - .hashCode, + imageFilePath: imageFilePath, + isGallerySave: isGallerySave, + ).hashCode, ); }); }); @@ -249,28 +279,35 @@ void main() { test('returns default file name prefix when saving on the web', () { // The browser handles name conflicts. for (final isGallerySave in {true, false}) { - expect(getDefaultImageFileName(isGallerySave: isGallerySave), - defaultImageFileNamePrefix); + expect( + getDefaultImageFileName(isGallerySave: isGallerySave), + defaultImageFileNamePrefix, + ); } }); } test('returns default file name prefix when saving to gallery', () { // The gallery app handles name conflicts. - expect(getDefaultImageFileName(isGallerySave: true), - defaultImageFileNamePrefix); + expect( + getDefaultImageFileName(isGallerySave: true), + defaultImageFileNamePrefix, + ); }); test( - 'returns default file name prefix for system save dialog on macOS and Windows', - () { - for (final platform in {TargetPlatform.macOS, TargetPlatform.windows}) { - debugDefaultTargetPlatformOverride = platform; - - expect(getDefaultImageFileName(isGallerySave: false), - defaultImageFileNamePrefix); - } - }); + 'returns default file name prefix for system save dialog on macOS and Windows', + () { + for (final platform in {TargetPlatform.macOS, TargetPlatform.windows}) { + debugDefaultTargetPlatformOverride = platform; + + expect( + getDefaultImageFileName(isGallerySave: false), + defaultImageFileNamePrefix, + ); + } + }, + ); test('returns unique file name for system save dialog image on Linux', () { debugDefaultTargetPlatformOverride = TargetPlatform.linux; @@ -326,15 +363,16 @@ void main() { group('shouldSaveToGallery', () { late MockQuillNativeBridge mockQuillNativeBridge; - void mockGallerySaveSupported(bool isSupported) => - when(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImageToGallery)) - .thenAnswer((_) async => isSupported); + void mockGallerySaveSupported(bool isSupported) => when( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).thenAnswer((_) async => isSupported); - void mockImageSaveSupported(bool isSupported) => - when(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)) - .thenAnswer((_) async => isSupported); + void mockImageSaveSupported(bool isSupported) => when( + () => + mockQuillNativeBridge.isSupported(QuillNativeBridgeFeature.saveImage), + ).thenAnswer((_) async => isSupported); setUp(() { mockQuillNativeBridge = MockQuillNativeBridge(); @@ -342,121 +380,173 @@ void main() { }); test( - 'returns false if gallery save not supported regardless of prefersGallerySave', - () async { - mockGallerySaveSupported(false); + 'returns false if gallery save not supported regardless of prefersGallerySave', + () async { + mockGallerySaveSupported(false); - for (final prefersGallerySave in {true, false}) { - final result = - await shouldSaveToGallery(prefersGallerySave: prefersGallerySave); - expect(result, isFalse); - verify(() => mockQuillNativeBridge.isSupported( - QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verifyNever(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)); - } - }); + for (final prefersGallerySave in {true, false}) { + final result = await shouldSaveToGallery( + prefersGallerySave: prefersGallerySave, + ); + expect(result, isFalse); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verifyNever( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ); + } + }, + ); test( - 'returns false when gallery save is not supported, regardless of if image save is supported', - () async { - mockGallerySaveSupported(false); + 'returns false when gallery save is not supported, regardless of if image save is supported', + () async { + mockGallerySaveSupported(false); - for (final isImageSupported in {true, false}) { - mockImageSaveSupported(isImageSupported); + for (final isImageSupported in {true, false}) { + mockImageSaveSupported(isImageSupported); - final result = await shouldSaveToGallery(prefersGallerySave: true); + final result = await shouldSaveToGallery(prefersGallerySave: true); - expect(result, isFalse); - verify(() => mockQuillNativeBridge.isSupported( - QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verifyNever(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)); - } - }); + expect(result, isFalse); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verifyNever( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ); + } + }, + ); test( - 'returns true if gallery save is supported and prefersGallerySave is true', - () async { - for (final imageSaveSupported in {true, false}) { - mockGallerySaveSupported(true); - mockImageSaveSupported(imageSaveSupported); + 'returns true if gallery save is supported and prefersGallerySave is true', + () async { + for (final imageSaveSupported in {true, false}) { + mockGallerySaveSupported(true); + mockImageSaveSupported(imageSaveSupported); - final result = await shouldSaveToGallery(prefersGallerySave: true); + final result = await shouldSaveToGallery(prefersGallerySave: true); - expect(result, isTrue); - verify(() => mockQuillNativeBridge.isSupported( - QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)).called(1); - } - }); + expect(result, isTrue); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ).called(1); + } + }, + ); test( - 'returns true when gallery and image save are supported and prefersGallerySave is true', - () async { - mockGallerySaveSupported(true); - mockImageSaveSupported(true); + 'returns true when gallery and image save are supported and prefersGallerySave is true', + () async { + mockGallerySaveSupported(true); + mockImageSaveSupported(true); - final result = await shouldSaveToGallery(prefersGallerySave: true); + final result = await shouldSaveToGallery(prefersGallerySave: true); - expect(result, isTrue); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)).called(1); - }); + expect(result, isTrue); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ).called(1); + }, + ); test( - 'returns false when gallery and image save are supported and prefersGallerySave is false', - () async { - mockGallerySaveSupported(true); - mockImageSaveSupported(true); + 'returns false when gallery and image save are supported and prefersGallerySave is false', + () async { + mockGallerySaveSupported(true); + mockImageSaveSupported(true); - final result = await shouldSaveToGallery(prefersGallerySave: false); + final result = await shouldSaveToGallery(prefersGallerySave: false); - expect(result, isFalse); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)).called(1); - }); + expect(result, isFalse); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ).called(1); + }, + ); test( - 'returns false when gallery save is not supported and image save is supported regardless of prefersGallerySave', - () async { - mockGallerySaveSupported(false); - mockImageSaveSupported(true); + 'returns false when gallery save is not supported and image save is supported regardless of prefersGallerySave', + () async { + mockGallerySaveSupported(false); + mockImageSaveSupported(true); - for (final prefersGallerySave in {true, false}) { - final result = - await shouldSaveToGallery(prefersGallerySave: prefersGallerySave); + for (final prefersGallerySave in {true, false}) { + final result = await shouldSaveToGallery( + prefersGallerySave: prefersGallerySave, + ); - expect(result, isFalse); - verify(() => mockQuillNativeBridge.isSupported( - QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verifyNever(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)); - } - }); + expect(result, isFalse); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verifyNever( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ); + } + }, + ); test( - 'returns true when gallery save supported and image save not supported regardless of prefersGallerySave', - () async { - mockGallerySaveSupported(true); - mockImageSaveSupported(false); + 'returns true when gallery save supported and image save not supported regardless of prefersGallerySave', + () async { + mockGallerySaveSupported(true); + mockImageSaveSupported(false); - for (final prefersGallerySave in {true, false}) { - final result = - await shouldSaveToGallery(prefersGallerySave: prefersGallerySave); + for (final prefersGallerySave in {true, false}) { + final result = await shouldSaveToGallery( + prefersGallerySave: prefersGallerySave, + ); - expect(result, isTrue); - verify(() => mockQuillNativeBridge.isSupported( - QuillNativeBridgeFeature.saveImageToGallery)).called(1); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)); - } - }); + expect(result, isTrue); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).called(1); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ); + } + }, + ); }); group('$ImageSaver', () { @@ -477,15 +567,16 @@ void main() { late MockImageLoader mockImageLoader; final imageSaver = ImageSaver.instance; - void mockGallerySaveSupported(bool isSupported) => - when(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImageToGallery)) - .thenAnswer((_) async => isSupported); + void mockGallerySaveSupported(bool isSupported) => when( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImageToGallery, + ), + ).thenAnswer((_) async => isSupported); - void mockImageSaveSupported(bool isSupported) => - when(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)) - .thenAnswer((_) async => isSupported); + void mockImageSaveSupported(bool isSupported) => when( + () => + mockQuillNativeBridge.isSupported(QuillNativeBridgeFeature.saveImage), + ).thenAnswer((_) async => isSupported); Future mockShouldSaveToGallery(bool shouldSaveToGalleryValue) async { if (shouldSaveToGalleryValue) { @@ -503,10 +594,11 @@ void main() { ); } - void mockLoadImageBytesValue(Uint8List? imageBytes) => - when(() => mockImageLoader.loadImageBytesFromImageProvider( - imageProvider: any(named: 'imageProvider'), - )).thenAnswer((_) async => imageBytes); + void mockLoadImageBytesValue(Uint8List? imageBytes) => when( + () => mockImageLoader.loadImageBytesFromImageProvider( + imageProvider: any(named: 'imageProvider'), + ), + ).thenAnswer((_) async => imageBytes); setUp(() { mockQuillNativeBridge = MockQuillNativeBridge(); @@ -517,23 +609,35 @@ void main() { mockGallerySaveSupported(false); mockImageSaveSupported(false); - when(() => - mockQuillNativeBridge.saveImage(any(), - options: any(named: 'options'))).thenAnswer( - (_) async => const ImageSaveResult(blobUrl: null, filePath: null)); + when( + () => mockQuillNativeBridge.saveImage( + any(), + options: any(named: 'options'), + ), + ).thenAnswer( + (_) async => const ImageSaveResult(blobUrl: null, filePath: null), + ); - when(() => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options'))).thenAnswer((_) async {}); + when( + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), + ).thenAnswer((_) async {}); mockLoadImageBytesValue(null); }); setUpAll(() { registerFallbackValue(Uint8List.fromList([])); registerFallbackValue( - const ImageSaveOptions(fileExtension: '', name: '')); + const ImageSaveOptions(fileExtension: '', name: ''), + ); registerFallbackValue( const GalleryImageSaveOptions( - albumName: '', name: '', fileExtension: ''), + albumName: '', + name: '', + fileExtension: '', + ), ); registerFallbackValue(FakeImageProvider()); }); @@ -549,59 +653,62 @@ void main() { ); }); - test('does not throw $ArgumentError when the image URL is not empty', - () async { - await imageSaver.saveImage( - imageProvider: FakeImageProvider(), - imageUrl: '/foo/bar', - prefersGallerySave: false, - ); - await expectLater( - imageSaver.saveImage( + test( + 'does not throw $ArgumentError when the image URL is not empty', + () async { + await imageSaver.saveImage( imageProvider: FakeImageProvider(), imageUrl: '/foo/bar', prefersGallerySave: false, - ), - completes, - ); - }); - - test('calls $ImageLoader to load the image bytes from the $ImageProvider', - () async { - await imageSaver.saveImage( - imageUrl: 'imageUrl', - imageProvider: FakeImageProvider(), - prefersGallerySave: false, - ); - verify( - () => mockImageLoader.loadImageBytesFromImageProvider( - imageProvider: any(named: 'imageProvider')), - ).called(1); - }); - - test( - 'returns null when image bytes are null or empty', - () async { - await mockShouldSaveToGallery(true); - - for (final imageBytes in {Uint8List.fromList([]), null}) { - mockLoadImageBytesValue(imageBytes); - - final result = await imageSaver.saveImage( + ); + await expectLater( + imageSaver.saveImage( imageProvider: FakeImageProvider(), imageUrl: '/foo/bar', prefersGallerySave: false, - ); - expect(result, isNull); + ), + completes, + ); + }, + ); - verify( - () => mockImageLoader.loadImageBytesFromImageProvider( - imageProvider: any(named: 'imageProvider')), - ).called(1); - } + test( + 'calls $ImageLoader to load the image bytes from the $ImageProvider', + () async { + await imageSaver.saveImage( + imageUrl: 'imageUrl', + imageProvider: FakeImageProvider(), + prefersGallerySave: false, + ); + verify( + () => mockImageLoader.loadImageBytesFromImageProvider( + imageProvider: any(named: 'imageProvider'), + ), + ).called(1); }, ); + test('returns null when image bytes are null or empty', () async { + await mockShouldSaveToGallery(true); + + for (final imageBytes in {Uint8List.fromList([]), null}) { + mockLoadImageBytesValue(imageBytes); + + final result = await imageSaver.saveImage( + imageProvider: FakeImageProvider(), + imageUrl: '/foo/bar', + prefersGallerySave: false, + ); + expect(result, isNull); + + verify( + () => mockImageLoader.loadImageBytesFromImageProvider( + imageProvider: any(named: 'imageProvider'), + ), + ).called(1); + } + }); + test( 'calls saveImageToGallery from $QuillNativeBridge when shouldSaveToGallery is true', () async { @@ -614,8 +721,10 @@ void main() { prefersGallerySave: false, ); verify( - () => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options')), + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), ).called(1); }, ); @@ -632,8 +741,10 @@ void main() { prefersGallerySave: false, ); verifyNever( - () => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options')), + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), ); }, ); @@ -652,70 +763,85 @@ void main() { ); expect( result, - const SaveImageResult( - isGallerySave: true, - imageFilePath: null, - ), + const SaveImageResult(isGallerySave: true, imageFilePath: null), ); verify( - () => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options')), + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), ).called(1); }, ); test( - 'throws $GalleryImageSaveAccessDeniedException in case permission is denied', - () async { - await mockShouldSaveToGallery(true); + 'throws $GalleryImageSaveAccessDeniedException in case permission is denied', + () async { + await mockShouldSaveToGallery(true); - mockLoadImageBytesValue(Uint8List.fromList([1, 2, 2])); + mockLoadImageBytesValue(Uint8List.fromList([1, 2, 2])); - final platformException = PlatformException(code: 'PERMISSION_DENIED'); - when(() => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options'))).thenThrow(platformException); + final platformException = PlatformException(code: 'PERMISSION_DENIED'); + when( + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), + ).thenThrow(platformException); - await expectLater( - imageSaver.saveImage( - imageProvider: FakeImageProvider(), - imageUrl: '/foo/bar', - prefersGallerySave: false, - ), - throwsA( - isA().having( - (e) => e.message, 'message', platformException.toString()), - ), - ); - }); + await expectLater( + imageSaver.saveImage( + imageProvider: FakeImageProvider(), + imageUrl: '/foo/bar', + prefersGallerySave: false, + ), + throwsA( + isA().having( + (e) => e.message, + 'message', + platformException.toString(), + ), + ), + ); + }, + ); test( - 'rethrows the $PlatformException in case permission is denied on macOS in debug-builds only (known macOS issue)', - () async { - debugDefaultTargetPlatformOverride = TargetPlatform.macOS; + 'rethrows the $PlatformException in case permission is denied on macOS in debug-builds only (known macOS issue)', + () async { + debugDefaultTargetPlatformOverride = TargetPlatform.macOS; - await mockShouldSaveToGallery(true); + await mockShouldSaveToGallery(true); - mockLoadImageBytesValue(Uint8List.fromList([1, 2, 2])); + mockLoadImageBytesValue(Uint8List.fromList([1, 2, 2])); - final platformException = PlatformException( - code: 'PERMISSION_DENIED', message: 'A known macOS issue'); - when(() => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options'))).thenThrow(platformException); + final platformException = PlatformException( + code: 'PERMISSION_DENIED', + message: 'A known macOS issue', + ); + when( + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), + ).thenThrow(platformException); - await expectLater( - imageSaver.saveImage( - imageProvider: FakeImageProvider(), - imageUrl: '/foo/bar', - prefersGallerySave: false, - ), - throwsA( - isA() - .having((e) => e.code, 'code', platformException.code) - .having((e) => e.message, 'message', platformException.message) - .having((e) => e.details, 'details', platformException.details), - ), - ); - }, skip: kReleaseMode); + await expectLater( + imageSaver.saveImage( + imageProvider: FakeImageProvider(), + imageUrl: '/foo/bar', + prefersGallerySave: false, + ), + throwsA( + isA() + .having((e) => e.code, 'code', platformException.code) + .having((e) => e.message, 'message', platformException.message) + .having((e) => e.details, 'details', platformException.details), + ), + ); + }, + skip: kReleaseMode, + ); test( 'rethrows the $PlatformException from $QuillNativeBridge if not handled', @@ -731,8 +857,12 @@ void main() { code: 'EXAMPLE_CODE_${DateTime.now().toIso8601String()}', message: 'An example exception that is not handled', ); - when(() => mockQuillNativeBridge.saveImageToGallery(any(), - options: any(named: 'options'))).thenThrow(exception); + when( + () => mockQuillNativeBridge.saveImageToGallery( + any(), + options: any(named: 'options'), + ), + ).thenThrow(exception); await expectLater( imageSaver.saveImage( @@ -758,8 +888,11 @@ void main() { prefersGallerySave: false, ); - verify(() => mockQuillNativeBridge - .isSupported(QuillNativeBridgeFeature.saveImage)).called(1); + verify( + () => mockQuillNativeBridge.isSupported( + QuillNativeBridgeFeature.saveImage, + ), + ).called(1); }, ); @@ -778,8 +911,12 @@ void main() { prefersGallerySave: false, ); - verify(() => mockQuillNativeBridge.saveImage(any(), - options: any(named: 'options'))).called(1); + verify( + () => mockQuillNativeBridge.saveImage( + any(), + options: any(named: 'options'), + ), + ).called(1); }, ); @@ -802,8 +939,12 @@ void main() { // Skip since another test handles it } - verifyNever(() => mockQuillNativeBridge.saveImage(any(), - options: any(named: 'options'))); + verifyNever( + () => mockQuillNativeBridge.saveImage( + any(), + options: any(named: 'options'), + ), + ); }, ); @@ -812,7 +953,7 @@ void main() { () async { for (final imageUrl in { 'path/to/file.png', - 'http://flutter-quill.org/file.png' + 'http://flutter-quill.org/file.png', }) { await mockShouldSaveToGallery(true); @@ -825,8 +966,9 @@ void main() { prefersGallerySave: false, ); - final imageFileExtension = - extractImageFileExtensionFromImageSource(imageUrl); + final imageFileExtension = extractImageFileExtensionFromImageSource( + imageUrl, + ); final imageName = extractImageNameFromImageSource(imageUrl); verify( @@ -848,7 +990,7 @@ void main() { () async { for (final imageUrl in { 'path/to/file.png', - 'http://flutter-quill.org/file.png' + 'http://flutter-quill.org/file.png', }) { await mockShouldSaveToGallery(false); @@ -863,8 +1005,9 @@ void main() { prefersGallerySave: false, ); - final imageFileExtension = - extractImageFileExtensionFromImageSource(imageUrl); + final imageFileExtension = extractImageFileExtensionFromImageSource( + imageUrl, + ); final imageName = extractImageNameFromImageSource(imageUrl); verify( @@ -881,39 +1024,42 @@ void main() { }, ); - test( - 'returns the $SaveImageResult correctly for image save', - () async { - await mockShouldSaveToGallery(false); + test('returns the $SaveImageResult correctly for image save', () async { + await mockShouldSaveToGallery(false); - final imageBytes = Uint8List.fromList([1, 0, 1]); - mockLoadImageBytesValue(imageBytes); + final imageBytes = Uint8List.fromList([1, 0, 1]); + mockLoadImageBytesValue(imageBytes); - mockImageSaveSupported(true); + mockImageSaveSupported(true); - const inputImagePath = 'path/to/example_file.png'; + const inputImagePath = 'path/to/example_file.png'; - const savedImagePath = '/path/to/saved/example_file.png'; + const savedImagePath = '/path/to/saved/example_file.png'; - when( - () => mockQuillNativeBridge.saveImage(imageBytes, - options: any(named: 'options')), - ).thenAnswer((_) async => - const ImageSaveResult(filePath: savedImagePath, blobUrl: null)); + when( + () => mockQuillNativeBridge.saveImage( + imageBytes, + options: any(named: 'options'), + ), + ).thenAnswer( + (_) async => + const ImageSaveResult(filePath: savedImagePath, blobUrl: null), + ); - final result = await imageSaver.saveImage( - imageProvider: FakeImageProvider(), - imageUrl: inputImagePath, - prefersGallerySave: false, - ); + final result = await imageSaver.saveImage( + imageProvider: FakeImageProvider(), + imageUrl: inputImagePath, + prefersGallerySave: false, + ); - expect( - result, - const SaveImageResult( - imageFilePath: savedImagePath, isGallerySave: false), - ); - }, - ); + expect( + result, + const SaveImageResult( + imageFilePath: savedImagePath, + isGallerySave: false, + ), + ); + }); test( 'throws $StateError when both image and gallery unsupported', @@ -930,8 +1076,13 @@ void main() { imageUrl: '/foo/bar', prefersGallerySave: false, ), - throwsA(isA().having((e) => e.message, 'message', - 'Image save is not handled on $defaultTargetPlatform')), + throwsA( + isA().having( + (e) => e.message, + 'message', + 'Image save is not handled on $defaultTargetPlatform', + ), + ), ); }, ); @@ -950,9 +1101,9 @@ class FakeImageProvider extends ImageProvider { class FakeImageSaver implements ImageSaver { @override - Future saveImage( - {required String imageUrl, - required ImageProvider imageProvider, - required bool prefersGallerySave}) => - throw UnimplementedError('Fake implementation of $FakeImageSaver'); + Future saveImage({ + required String imageUrl, + required ImageProvider imageProvider, + required bool prefersGallerySave, + }) => throw UnimplementedError('Fake implementation of $FakeImageSaver'); } diff --git a/flutter_quill_extensions/test/editor/image/widgets/image_menu_test.dart b/flutter_quill_extensions/test/editor/image/widgets/image_menu_test.dart index bf067f956..96a1355b2 100644 --- a/flutter_quill_extensions/test/editor/image/widgets/image_menu_test.dart +++ b/flutter_quill_extensions/test/editor/image/widgets/image_menu_test.dart @@ -49,15 +49,19 @@ void main() { ImageProvider? imageProvider, bool prefersGallerySave = false, }) async { - await tester.pumpWidget(QuillTestApp.withScaffold(ImageOptionsMenu( - controller: controller, - config: const QuillEditorImageEmbedConfig(), - imageProvider: imageProvider ?? FakeImageProvider(), - imageSource: imageSource, - readOnly: true, - imageSize: const ElementSize(200, 300), - prefersGallerySave: prefersGallerySave, - ))); + await tester.pumpWidget( + QuillTestApp.withScaffold( + ImageOptionsMenu( + controller: controller, + config: const QuillEditorImageEmbedConfig(), + imageProvider: imageProvider ?? FakeImageProvider(), + imageSource: imageSource, + readOnly: true, + imageSize: const ElementSize(200, 300), + prefersGallerySave: prefersGallerySave, + ), + ), + ); } Finder findTargetWidget() { @@ -67,20 +71,20 @@ void main() { } void mockSaveImageResult(SaveImageResult? result) => when( - () => mockImageSaver.saveImage( - imageUrl: any(named: 'imageUrl'), - imageProvider: any(named: 'imageProvider'), - prefersGallerySave: any(named: 'prefersGallerySave'), - ), - ).thenAnswer((_) async => result); + () => mockImageSaver.saveImage( + imageUrl: any(named: 'imageUrl'), + imageProvider: any(named: 'imageProvider'), + prefersGallerySave: any(named: 'prefersGallerySave'), + ), + ).thenAnswer((_) async => result); void mockSaveImageThrows(Exception exception) => when( - () => mockImageSaver.saveImage( - imageUrl: any(named: 'imageUrl'), - imageProvider: any(named: 'imageProvider'), - prefersGallerySave: any(named: 'prefersGallerySave'), - ), - ).thenThrow(exception); + () => mockImageSaver.saveImage( + imageUrl: any(named: 'imageUrl'), + imageProvider: any(named: 'imageProvider'), + prefersGallerySave: any(named: 'prefersGallerySave'), + ), + ).thenThrow(exception); Future tapTargetWidget(WidgetTester tester) async { await tester.tap(findTargetWidget()); @@ -107,14 +111,16 @@ void main() { testWidgets( 'shows a success message when the image is downloaded on the web.', (tester) async { - mockSaveImageResult(const SaveImageResult( - imageFilePath: null, isGallerySave: false)); + mockSaveImageResult( + const SaveImageResult(imageFilePath: null, isGallerySave: false), + ); await pumpTargetWidget(tester); await tapTargetWidget(tester); - final localizations = - tester.localizationsFromElement(ImageOptionsMenu); + final localizations = tester.localizationsFromElement( + ImageOptionsMenu, + ); expect( find.text(localizations.successImageDownloaded), @@ -125,48 +131,36 @@ void main() { } testWidgets( - 'shows permission denied message only when permission is denied', - (tester) async { - mockSaveImageThrows(GalleryImageSaveAccessDeniedException()); + 'shows permission denied message only when permission is denied', + (tester) async { + mockSaveImageThrows(GalleryImageSaveAccessDeniedException()); - await pumpTargetWidget(tester); - await tapTargetWidget(tester); + await pumpTargetWidget(tester); + await tapTargetWidget(tester); - final localizations = tester.localizationsFromElement(ImageOptionsMenu); + final localizations = tester.localizationsFromElement( + ImageOptionsMenu, + ); - expect( - find.text(localizations.saveImagePermissionDenied), - findsOneWidget, - ); - expect( - find.text(localizations.errorUnexpectedSavingImage), - findsNothing, - ); - expect( - find.text(localizations.successImageDownloaded), - findsNothing, - ); - expect( - find.text(localizations.successImageSavedGallery), - findsNothing, - ); - expect( - find.text(localizations.successImageSaved), - findsNothing, - ); - expect( - find.text(localizations.openFileLocation), - findsNothing, - ); - expect( - find.text(localizations.openFile), - findsNothing, - ); - expect( - find.text(localizations.openGallery), - findsNothing, - ); - }); + expect( + find.text(localizations.saveImagePermissionDenied), + findsOneWidget, + ); + expect( + find.text(localizations.errorUnexpectedSavingImage), + findsNothing, + ); + expect(find.text(localizations.successImageDownloaded), findsNothing); + expect( + find.text(localizations.successImageSavedGallery), + findsNothing, + ); + expect(find.text(localizations.successImageSaved), findsNothing); + expect(find.text(localizations.openFileLocation), findsNothing); + expect(find.text(localizations.openFile), findsNothing); + expect(find.text(localizations.openGallery), findsNothing); + }, + ); testWidgets('shows error message when saving fails', (tester) async { mockSaveImageResult(null); @@ -190,10 +184,15 @@ void main() { ); }); - testWidgets('shows saved and open gallery on gallery save', - (tester) async { - mockSaveImageResult(const SaveImageResult( - imageFilePath: 'path/to/file', isGallerySave: true)); + testWidgets('shows saved and open gallery on gallery save', ( + tester, + ) async { + mockSaveImageResult( + const SaveImageResult( + imageFilePath: 'path/to/file', + isGallerySave: true, + ), + ); await pumpTargetWidget(tester); @@ -206,98 +205,109 @@ void main() { findsOneWidget, ); - expect( - find.text(localizations.openGallery), - findsOneWidget, - ); + expect(find.text(localizations.openGallery), findsOneWidget); }); for (final desktopPlatform in TargetPlatformVariant.desktop().values) { testWidgets( - 'shows saved success image and open file path action on ${desktopPlatform.name}', - (tester) async { - debugDefaultTargetPlatformOverride = desktopPlatform; - - const savedImagePath = 'path/to/file'; - mockSaveImageResult(const SaveImageResult( - imageFilePath: savedImagePath, isGallerySave: false)); + 'shows saved success image and open file path action on ${desktopPlatform.name}', + (tester) async { + debugDefaultTargetPlatformOverride = desktopPlatform; + + const savedImagePath = 'path/to/file'; + mockSaveImageResult( + const SaveImageResult( + imageFilePath: savedImagePath, + isGallerySave: false, + ), + ); - await pumpTargetWidget(tester); - await tapTargetWidget(tester); + await pumpTargetWidget(tester); + await tapTargetWidget(tester); - final localizations = - tester.localizationsFromElement(ImageOptionsMenu); + final localizations = tester.localizationsFromElement( + ImageOptionsMenu, + ); - expect( - find.text(localizations.successImageSaved), - findsOneWidget, - ); + expect(find.text(localizations.successImageSaved), findsOneWidget); - expect( - find.text(defaultTargetPlatform == TargetPlatform.macOS - ? localizations.openFile - : localizations.openFileLocation), - findsOneWidget, - ); + expect( + find.text( + defaultTargetPlatform == TargetPlatform.macOS + ? localizations.openFile + : localizations.openFileLocation, + ), + findsOneWidget, + ); - debugDefaultTargetPlatformOverride = null; - }); + debugDefaultTargetPlatformOverride = null; + }, + ); } for (final prefersGallerySave in {true, false}) { testWidgets( - 'passes the arguments correctly to saveImage from $ImageSaver when prefersGallerySave is $prefersGallerySave', - (tester) async { - mockSaveImageResult( - const SaveImageResult(imageFilePath: null, isGallerySave: true), - ); + 'passes the arguments correctly to saveImage from $ImageSaver when prefersGallerySave is $prefersGallerySave', + (tester) async { + mockSaveImageResult( + const SaveImageResult(imageFilePath: null, isGallerySave: true), + ); - const imageUrl = 'http://flutter-quill.org/image.webp'; - final imageProvider = AnotherFakeImageProvider(); + const imageUrl = 'http://flutter-quill.org/image.webp'; + final imageProvider = AnotherFakeImageProvider(); - await pumpTargetWidget( - tester, - imageSource: imageUrl, - prefersGallerySave: prefersGallerySave, - imageProvider: imageProvider, - ); + await pumpTargetWidget( + tester, + imageSource: imageUrl, + prefersGallerySave: prefersGallerySave, + imageProvider: imageProvider, + ); - await tapTargetWidget(tester); + await tapTargetWidget(tester); - verify( - () => mockImageSaver.saveImage( + verify( + () => mockImageSaver.saveImage( imageUrl: imageUrl, imageProvider: imageProvider, - prefersGallerySave: prefersGallerySave), - ).called(1); - }); + prefersGallerySave: prefersGallerySave, + ), + ).called(1); + }, + ); } - testWidgets('throws $StateError when save result is not handled', - (tester) async { + testWidgets('throws $StateError when save result is not handled', ( + tester, + ) async { debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; mockSaveImageResult( - const SaveImageResult(imageFilePath: null, isGallerySave: false)); + const SaveImageResult(imageFilePath: null, isGallerySave: false), + ); Object? capturedException; - await runZonedGuarded(() async { - await pumpTargetWidget(tester); + await runZonedGuarded( + () async { + await pumpTargetWidget(tester); - await tapTargetWidget(tester); - }, (error, stackTrace) { - capturedException = error; - }); + await tapTargetWidget(tester); + }, + (error, stackTrace) { + capturedException = error; + }, + ); expect( - capturedException, - isA().having( - (e) => e.message, - 'message', - equals( - 'Image save result is not handled on $defaultTargetPlatform'), - )); + capturedException, + isA().having( + (e) => e.message, + 'message', + equals( + 'Image save result is not handled on $defaultTargetPlatform', + ), + ), + ); debugDefaultTargetPlatformOverride = null; }); diff --git a/flutter_quill_extensions/test/quill_test_app.dart b/flutter_quill_extensions/test/quill_test_app.dart index 64f53ad16..f78718313 100644 --- a/flutter_quill_extensions/test/quill_test_app.dart +++ b/flutter_quill_extensions/test/quill_test_app.dart @@ -4,11 +4,7 @@ import 'package:flutter_quill/internal.dart'; import 'package:flutter_test/flutter_test.dart'; class QuillTestApp extends StatelessWidget { - QuillTestApp({ - required this.home, - required this.scaffoldBody, - super.key, - }) { + QuillTestApp({required this.home, required this.scaffoldBody, super.key}) { if (home != null && scaffoldBody != null) { throw ArgumentError('Either the home or scaffoldBody must be null'); } @@ -28,10 +24,7 @@ class QuillTestApp extends StatelessWidget { return MaterialApp( localizationsDelegates: FlutterQuillLocalizations.localizationsDelegates, supportedLocales: FlutterQuillLocalizations.supportedLocales, - home: home ?? - Scaffold( - body: scaffoldBody, - ), + home: home ?? Scaffold(body: scaffoldBody), ); } } diff --git a/flutter_quill_test/CHANGELOG.md b/flutter_quill_test/CHANGELOG.md index 2e1107f30..7775b39db 100644 --- a/flutter_quill_test/CHANGELOG.md +++ b/flutter_quill_test/CHANGELOG.md @@ -10,6 +10,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- Updated minimum supported SDK version to Flutter 3.44/Dart 3.12 to align with `flutter_quill`. + ## [11.1.0] - 2025-03-19 ### Added diff --git a/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_cursor_movement_extension.dart b/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_cursor_movement_extension.dart index 1b250a47f..0bcc35811 100644 --- a/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_cursor_movement_extension.dart +++ b/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_cursor_movement_extension.dart @@ -23,9 +23,7 @@ extension QuillWidgetTesterCursorMovementExtension on WidgetTester { testTextInput.updateEditingValue( TextEditingValue( text: editor.textEditingValue.text, - selection: TextSelection.collapsed( - offset: index, - ), + selection: TextSelection.collapsed(offset: index), ), ); await idle(); diff --git a/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_nodes_selection_extension.dart b/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_nodes_selection_extension.dart index 0249f3266..93203526b 100644 --- a/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_nodes_selection_extension.dart +++ b/flutter_quill_test/lib/src/test/selection_interactions/widget_tester_nodes_selection_extension.dart @@ -23,14 +23,22 @@ extension QuillWidgetTesterNodesSelectionExtension on WidgetTester { final editor = findRawEditor(editorFinder); final selection = editor.textEditingValue.selection; if (!selection.isValid || selection.isCollapsed) return []; - final start = - editor.controller.document.queryChild(selection.baseOffset).node; - final end = - editor.controller.document.queryChild(selection.extentOffset).node; - expect(start, isNotNull, - reason: 'The node at offset: ${selection.start} was not found'); - expect(end, isNotNull, - reason: 'The node at offset: ${selection.end} was not found'); + final start = editor.controller.document + .queryChild(selection.baseOffset) + .node; + final end = editor.controller.document + .queryChild(selection.extentOffset) + .node; + expect( + start, + isNotNull, + reason: 'The node at offset: ${selection.start} was not found', + ); + expect( + end, + isNotNull, + reason: 'The node at offset: ${selection.end} was not found', + ); if (start == end) { return [start!]; } @@ -67,12 +75,17 @@ extension QuillWidgetTesterNodesSelectionExtension on WidgetTester { final editor = findRawEditor(editorFinder); final selection = editor.textEditingValue.selection; if (!selection.isValid) return null; - final start = - editor.controller.document.queryChild(selection.baseOffset).node; - final end = - editor.controller.document.queryChild(selection.extentOffset).node; - expect(start, isNotNull, - reason: 'The node at offset: ${start?.documentOffset} was not found'); + final start = editor.controller.document + .queryChild(selection.baseOffset) + .node; + final end = editor.controller.document + .queryChild(selection.extentOffset) + .node; + expect( + start, + isNotNull, + reason: 'The node at offset: ${start?.documentOffset} was not found', + ); final isSelectionIntoSameNode = start == end; if (isSelectionIntoSameNode) { return start!; diff --git a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_insert_extension.dart b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_insert_extension.dart index cbbf9e71d..d1cdf8a04 100644 --- a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_insert_extension.dart +++ b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_insert_extension.dart @@ -42,13 +42,21 @@ extension QuillWidgetTesterInsertionExt on WidgetTester { /// find.byType(QuillEditor) /// ``` Future quillEnterTextAtPosition( - Finder finder, String textInsert, int index) async { - expect(index, isNonNegative, - reason: 'Index passed cannot be less than zero'); + Finder finder, + String textInsert, + int index, + ) async { + expect( + index, + isNonNegative, + reason: 'Index passed cannot be less than zero', + ); final editor = findRawEditor(finder); - final plainText = editor.controller.document - .toPlainText() - .replaceRange(index, index, textInsert); + final plainText = editor.controller.document.toPlainText().replaceRange( + index, + index, + textInsert, + ); return TestAsyncUtils.guard(() async { await quillGiveFocus(finder); await quillUpdateEditingValueWithSelection( diff --git a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_remove_extension.dart b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_remove_extension.dart index 171673969..e83e09601 100644 --- a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_remove_extension.dart +++ b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_remove_extension.dart @@ -29,9 +29,7 @@ extension QuillWidgetTesterRemoveExt on WidgetTester { await quillUpdateEditingValueWithSelection( finder, plainTextRemoved, - TextSelection.collapsed( - offset: selection.baseOffset, - ), + TextSelection.collapsed(offset: selection.baseOffset), ); await idle(); }); @@ -54,8 +52,11 @@ extension QuillWidgetTesterRemoveExt on WidgetTester { /// find.byType(QuillEditor) /// ``` Future quillRemoveText(Finder finder, TextSelection selection) async { - expect(selection.isValid, isTrue, - reason: 'The selection passed for removing text is not valid'); + expect( + selection.isValid, + isTrue, + reason: 'The selection passed for removing text is not valid', + ); final editor = findRawEditor(finder); final plainTextRemoved = editor.controller.document .toPlainText() diff --git a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_replace_extension.dart b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_replace_extension.dart index 49d4a779c..1ff3f3c30 100644 --- a/flutter_quill_test/lib/src/test/text_interactions/widget_tester_replace_extension.dart +++ b/flutter_quill_test/lib/src/test/text_interactions/widget_tester_replace_extension.dart @@ -20,14 +20,23 @@ extension QuillWidgetTesterReplaceExt on WidgetTester { /// find.byType(QuillEditor) /// ``` Future quillReplaceTextWithSelection( - Finder finder, String replacement, TextSelection selection) async { + Finder finder, + String replacement, + TextSelection selection, + ) async { final editor = findRawEditor(finder); - expect(selection.isValid, isTrue, - reason: 'The selection in the editor is not valid'); + expect( + selection.isValid, + isTrue, + reason: 'The selection in the editor is not valid', + ); final effectivePlainText = editor.controller.document .toPlainText() .replaceRange( - selection.baseOffset, selection.extentOffset, replacement); + selection.baseOffset, + selection.extentOffset, + replacement, + ); return TestAsyncUtils.guard(() async { await quillGiveFocus(finder); await quillUpdateEditingValueWithSelection( @@ -59,12 +68,18 @@ extension QuillWidgetTesterReplaceExt on WidgetTester { Future quillReplaceText(Finder finder, String replacement) async { final editor = findRawEditor(finder); final selection = editor.controller.selection; - expect(selection.isValid, isTrue, - reason: 'The selection in the editor is not valid'); + expect( + selection.isValid, + isTrue, + reason: 'The selection in the editor is not valid', + ); final effectivePlainText = editor.controller.document .toPlainText() .replaceRange( - selection.baseOffset, selection.extentOffset, replacement); + selection.baseOffset, + selection.extentOffset, + replacement, + ); return TestAsyncUtils.guard(() async { await quillGiveFocus(finder); await quillUpdateEditingValueWithSelection( diff --git a/flutter_quill_test/lib/src/test/widget_tester_extension.dart b/flutter_quill_test/lib/src/test/widget_tester_extension.dart index d2826294f..3ddee4dde 100644 --- a/flutter_quill_test/lib/src/test/widget_tester_extension.dart +++ b/flutter_quill_test/lib/src/test/widget_tester_extension.dart @@ -16,10 +16,7 @@ extension QuillWidgetTesterExt on WidgetTester { final focusNode = findRawEditor(finder).widget.config.focusNode ..requestFocus(); await pump(); - expect( - focusNode.hasFocus, - isTrue, - ); + expect(focusNode.hasFocus, isTrue); }); } @@ -92,16 +89,19 @@ extension QuillWidgetTesterExt on WidgetTester { /// find.byType(QuillEditor) /// ``` Future quillUpdateEditingValueWithSelection( - Finder finder, String text, TextSelection selection) async { - expect(selection.isValid, isTrue, - reason: - 'The TextSelection passed is not valid to be used for text editing values'); + Finder finder, + String text, + TextSelection selection, + ) async { + expect( + selection.isValid, + isTrue, + reason: + 'The TextSelection passed is not valid to be used for text editing values', + ); return TestAsyncUtils.guard(() async { testTextInput.updateEditingValue( - TextEditingValue( - text: text, - selection: selection, - ), + TextEditingValue(text: text, selection: selection), ); await idle(); }); @@ -129,7 +129,8 @@ extension QuillWidgetTesterExt on WidgetTester { TextEditingValue( text: text, selection: TextSelection.collapsed( - offset: editor.textEditingValue.text.length), + offset: editor.textEditingValue.text.length, + ), ), ); await idle(); @@ -153,8 +154,10 @@ extension QuillWidgetTesterExt on WidgetTester { return state( find.descendant( of: finder ?? find.byType(QuillEditor), - matching: find.byType(QuillRawEditor, - skipOffstage: finder?.skipOffstage ?? true), + matching: find.byType( + QuillRawEditor, + skipOffstage: finder?.skipOffstage ?? true, + ), matchRoot: true, ), ); diff --git a/flutter_quill_test/pubspec.yaml b/flutter_quill_test/pubspec.yaml index 4ab936042..b814008b1 100644 --- a/flutter_quill_test/pubspec.yaml +++ b/flutter_quill_test/pubspec.yaml @@ -8,8 +8,8 @@ documentation: https://github.com/singerdmx/flutter-quill/tree/master/flutter_qu topics: [testing, text, rich-text-editor, flutter-quill] environment: - sdk: '>=3.0.0 <4.0.0' - flutter: ">=1.17.0" + sdk: ^3.12.0 + flutter: ">=3.44.0" dependencies: flutter: diff --git a/flutter_quill_test/test/flutter_quill_test_test.dart b/flutter_quill_test/test/flutter_quill_test_test.dart index 46ce97d12..6d48ea142 100644 --- a/flutter_quill_test/test/flutter_quill_test_test.dart +++ b/flutter_quill_test/test/flutter_quill_test_test.dart @@ -17,11 +17,7 @@ void main() { testWidgets('Test QuillEditor interactions', (tester) async { // Build the QuillEditor widget await tester.pumpWidget( - MaterialApp( - home: QuillEditor.basic( - controller: controller, - ), - ), + MaterialApp(home: QuillEditor.basic(controller: controller)), ); await tester.tap(find.byType(QuillEditor)); @@ -35,8 +31,10 @@ void main() { expect(controller.selection, const TextSelection.collapsed(offset: 12)); await tester.quillExpandSelectionTo(find.byType(QuillEditor), 13); - expect(controller.selection, - const TextSelection(baseOffset: 12, extentOffset: 13)); + expect( + controller.selection, + const TextSelection(baseOffset: 12, extentOffset: 13), + ); await tester.quillReplaceText(find.byType(QuillEditor), ' and hi, World!'); expect(controller.document.toPlainText(), 'Hello, World and hi, World!\n'); @@ -47,8 +45,10 @@ void main() { await tester.quillExpandSelectionTo(find.byType(QuillEditor), 7); expect(controller.selection.isCollapsed, isFalse); - expect(controller.selection, - const TextSelection(baseOffset: 0, extentOffset: 7)); + expect( + controller.selection, + const TextSelection(baseOffset: 0, extentOffset: 7), + ); await tester.quillRemoveTextInSelection(find.byType(QuillEditor)); expect(controller.document.toPlainText(), 'World and hi, World!\n'); diff --git a/l10n.yaml b/l10n.yaml index 19167d040..1c6cac339 100644 --- a/l10n.yaml +++ b/l10n.yaml @@ -3,7 +3,6 @@ template-arb-file: quill_en.arb output-localization-file: quill_localizations.dart output-class: FlutterQuillLocalizations output-dir: lib/src/l10n/generated -synthetic-package: false format: true untranslated-messages-file: lib/src/l10n/untranslated.json nullable-getter: true diff --git a/lib/src/common/extensions/view_id_ext.dart b/lib/src/common/extensions/view_id_ext.dart new file mode 100644 index 000000000..dd941669e --- /dev/null +++ b/lib/src/common/extensions/view_id_ext.dart @@ -0,0 +1,11 @@ +import 'package:flutter/widgets.dart'; + +extension ViewIdExt on BuildContext { + int? getViewId() { + late final platformDispatcher = WidgetsBinding.instance.platformDispatcher; + return View.maybeOf(this)?.viewId ?? + // If context has no View, check platformDispatcher + platformDispatcher.views.firstOrNull?.viewId ?? + platformDispatcher.implicitView?.viewId; + } +} diff --git a/lib/src/common/structs/horizontal_spacing.dart b/lib/src/common/structs/horizontal_spacing.dart index d1beda8d5..9d7a249fa 100644 --- a/lib/src/common/structs/horizontal_spacing.dart +++ b/lib/src/common/structs/horizontal_spacing.dart @@ -2,32 +2,21 @@ import 'package:flutter/foundation.dart' show immutable; @immutable class HorizontalSpacing { - const HorizontalSpacing( - this.left, - this.right, - ); + const HorizontalSpacing(this.left, this.right); final double left; final double right; static const zero = HorizontalSpacing(0, 0); - HorizontalSpacing copyWith({ - double? left, - double? right, - }) { - return HorizontalSpacing( - left ?? this.left, - right ?? this.right, - ); + HorizontalSpacing copyWith({double? left, double? right}) { + return HorizontalSpacing(left ?? this.left, right ?? this.right); } @override bool operator ==(Object other) => identical(this, other) || - other is HorizontalSpacing && - left == other.left && - right == other.right; + other is HorizontalSpacing && left == other.left && right == other.right; @override int get hashCode => Object.hash(left, right); diff --git a/lib/src/common/structs/image_url.dart b/lib/src/common/structs/image_url.dart index e159b2a8a..a03f3077e 100644 --- a/lib/src/common/structs/image_url.dart +++ b/lib/src/common/structs/image_url.dart @@ -2,10 +2,7 @@ import 'package:flutter/foundation.dart' show immutable; @immutable class ImageUrl { - const ImageUrl( - this.url, - this.styleString, - ); + const ImageUrl(this.url, this.styleString); final String url; final String styleString; diff --git a/lib/src/common/structs/optional_size.dart b/lib/src/common/structs/optional_size.dart index 1462ab004..dad6a74d9 100644 --- a/lib/src/common/structs/optional_size.dart +++ b/lib/src/common/structs/optional_size.dart @@ -2,10 +2,7 @@ import 'package:flutter/foundation.dart' show immutable; @immutable class OptionalSize { - const OptionalSize( - this.width, - this.height, - ); + const OptionalSize(this.width, this.height); /// If non-null, requires the child to have exactly this width. /// If null, the child is free to choose its own width. @@ -15,13 +12,7 @@ class OptionalSize { /// If null, the child is free to choose its own height. final double? height; - OptionalSize copyWith({ - double? width, - double? height, - }) { - return OptionalSize( - width ?? this.width, - height ?? this.height, - ); + OptionalSize copyWith({double? width, double? height}) { + return OptionalSize(width ?? this.width, height ?? this.height); } } diff --git a/lib/src/common/structs/vertical_spacing.dart b/lib/src/common/structs/vertical_spacing.dart index 2c0398117..f57070014 100644 --- a/lib/src/common/structs/vertical_spacing.dart +++ b/lib/src/common/structs/vertical_spacing.dart @@ -2,32 +2,21 @@ import 'package:flutter/foundation.dart' show immutable; @immutable class VerticalSpacing { - const VerticalSpacing( - this.top, - this.bottom, - ); + const VerticalSpacing(this.top, this.bottom); final double top; final double bottom; static const zero = VerticalSpacing(0, 0); - VerticalSpacing copyWith({ - double? top, - double? bottom, - }) { - return VerticalSpacing( - top ?? this.top, - bottom ?? this.bottom, - ); + VerticalSpacing copyWith({double? top, double? bottom}) { + return VerticalSpacing(top ?? this.top, bottom ?? this.bottom); } @override bool operator ==(Object other) => identical(this, other) || - other is VerticalSpacing && - top == other.top && - bottom == other.bottom; + other is VerticalSpacing && top == other.top && bottom == other.bottom; @override int get hashCode => Object.hash(top, bottom); diff --git a/lib/src/common/utils/color.dart b/lib/src/common/utils/color.dart index 06724af61..a1e97caf9 100644 --- a/lib/src/common/utils/color.dart +++ b/lib/src/common/utils/color.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; import '../../editor/widgets/default_styles.dart'; -Color stringToColor(String? s, - [Color? originalColor, DefaultStyles? defaultStyles]) { +Color stringToColor( + String? s, [ + Color? originalColor, + DefaultStyles? defaultStyles, +]) { final palette = defaultStyles?.palette; if (s != null && palette != null) { final maybeColor = palette[s]; @@ -119,8 +122,12 @@ Color stringToColor(String? s, s = s.substring(5); // trim left 'rgba(' s = s.substring(0, s.length - 1); // trim right ')' final arr = s.split(',').map((e) => e.trim()).toList(); - return Color.fromRGBO(int.parse(arr[0]), int.parse(arr[1]), - int.parse(arr[2]), double.parse(arr[3])); + return Color.fromRGBO( + int.parse(arr[0]), + int.parse(arr[1]), + int.parse(arr[2]), + double.parse(arr[3]), + ); } // TODO: take care of "color": "inherit" diff --git a/lib/src/common/utils/font.dart b/lib/src/common/utils/font.dart index d12ffd00c..9330fe8be 100644 --- a/lib/src/common/utils/font.dart +++ b/lib/src/common/utils/font.dart @@ -22,8 +22,10 @@ dynamic getFontSize(dynamic sizeValue) { return fontSize; } -double? getFontSizeAsDouble(dynamic sizeValue, - {required DefaultStyles defaultStyles}) { +double? getFontSizeAsDouble( + dynamic sizeValue, { + required DefaultStyles defaultStyles, +}) { if (sizeValue is String && ['small', 'normal', 'large', 'huge'].contains(sizeValue)) { return switch (sizeValue) { diff --git a/lib/src/common/utils/link_validator.dart b/lib/src/common/utils/link_validator.dart index 36f9f09d6..69b95d36d 100644 --- a/lib/src/common/utils/link_validator.dart +++ b/lib/src/common/utils/link_validator.dart @@ -67,7 +67,7 @@ abstract final class LinkValidator { 'sip:', // Lync 'whatsapp:', 'http://', - 'https://' + 'https://', ]; static bool validate( @@ -92,7 +92,8 @@ abstract final class LinkValidator { return true; } // Implemented for backward compatibility, clients should use validateLink instead. - return (linkPrefixes + (legacyAddationalLinkPrefixes ?? [])) - .any((prefix) => link.startsWith(prefix)); + return (linkPrefixes + (legacyAddationalLinkPrefixes ?? [])).any( + (prefix) => link.startsWith(prefix), + ); } } diff --git a/lib/src/common/utils/string.dart b/lib/src/common/utils/string.dart index 5ca636993..4706aefd7 100644 --- a/lib/src/common/utils/string.dart +++ b/lib/src/common/utils/string.dart @@ -33,7 +33,7 @@ Alignment getAlignment(String? cssAlignment) { 'centerRight', 'bottomLeft', 'bottomCenter', - 'bottomRight' + 'bottomRight', ].indexOf(cssAlignment); if (index < 0) { return defaultAlignment; @@ -48,7 +48,7 @@ Alignment getAlignment(String? cssAlignment) { Alignment.centerRight, Alignment.bottomLeft, Alignment.bottomCenter, - Alignment.bottomRight + Alignment.bottomRight, ][index]; } diff --git a/lib/src/common/utils/widgets.dart b/lib/src/common/utils/widgets.dart index 9c7b4be38..273288d9d 100644 --- a/lib/src/common/utils/widgets.dart +++ b/lib/src/common/utils/widgets.dart @@ -6,13 +6,10 @@ typedef WidgetWrapper = Widget Function(Widget child); abstract final class UtilityWidgets { /// Conditionally wraps the [child] with [Tooltip] widget if [message] /// is not null and not empty. - static Widget maybeTooltip({ - required Widget child, - String? message, - }) => + static Widget maybeTooltip({required Widget child, String? message}) => (message?.isNotEmpty ?? false) - ? Tooltip(message: message, child: child) - : child; + ? Tooltip(message: message, child: child) + : child; /// Conditionally wraps the [child] with [wrapper] widget if [enabled] /// is true. @@ -20,6 +17,5 @@ abstract final class UtilityWidgets { required WidgetWrapper wrapper, required Widget child, bool enabled = false, - }) => - enabled ? wrapper(child) : child; + }) => enabled ? wrapper(child) : child; } diff --git a/lib/src/controller/clipboard/quill_controller_paste.dart b/lib/src/controller/clipboard/quill_controller_paste.dart index 26ec4511e..8649f2eef 100644 --- a/lib/src/controller/clipboard/quill_controller_paste.dart +++ b/lib/src/controller/clipboard/quill_controller_paste.dart @@ -19,15 +19,19 @@ extension QuillControllerPaste on QuillController { if (clipboardText == pastePlainText && pastePlainText.isNotEmpty && pasteDelta.isNotEmpty) { - replaceText(selection.start, selection.end - selection.start, - pasteDelta, TextSelection.collapsed(offset: selection.end)); + replaceText( + selection.start, + selection.end - selection.start, + pasteDelta, + TextSelection.collapsed(offset: selection.end), + ); } else { replaceText( - selection.start, - selection.end - selection.start, - clipboardText, - TextSelection.collapsed( - offset: selection.end + clipboardText.length)); + selection.start, + selection.end - selection.start, + clipboardText, + TextSelection.collapsed(offset: selection.end + clipboardText.length), + ); } return true; } diff --git a/lib/src/controller/quill_controller.dart b/lib/src/controller/quill_controller.dart index 0d2d291a4..b27be0264 100644 --- a/lib/src/controller/quill_controller.dart +++ b/lib/src/controller/quill_controller.dart @@ -27,8 +27,8 @@ typedef DeleteCallback = void Function(int cursorPosition, bool forward); class QuillController extends ChangeNotifier { QuillController({ - required Document document, - required TextSelection selection, + required this._document, + required this._selection, this.config = const QuillControllerConfig(), this.keepStyleOnNewLine = true, this.onReplaceText, @@ -36,17 +36,15 @@ class QuillController extends ChangeNotifier { this.onSelectionCompleted, this.onSelectionChanged, this.readOnly = false, - }) : _document = document, - _selection = selection; + }); factory QuillController.basic({ QuillControllerConfig config = const QuillControllerConfig(), - }) => - QuillController( - config: config, - document: Document(), - selection: const TextSelection.collapsed(offset: 0), - ); + }) => QuillController( + config: config, + document: Document(), + selection: const TextSelection.collapsed(offset: 0), + ); final QuillControllerConfig config; @@ -132,10 +130,8 @@ class QuillController extends ChangeNotifier { Stream get changes => document.changes; - TextEditingValue get plainTextEditingValue => TextEditingValue( - text: document.toPlainText(), - selection: selection, - ); + TextEditingValue get plainTextEditingValue => + TextEditingValue(text: document.toPlainText(), selection: selection); /// Only attributes applied to all characters within this range are /// included in the result. @@ -183,10 +179,8 @@ class QuillController extends ChangeNotifier { for (final style in styles) { final indent = style.value.attributes[Attribute.indent.key]; final formatIndex = math.max(style.offset, selection.start); - final formatLength = math.min( - style.offset + (style.length ?? 0), - selection.end, - ) - + final formatLength = + math.min(style.offset + (style.length ?? 0), selection.end) - style.offset; Attribute? formatAttribute; if (indent == null) { @@ -212,22 +206,27 @@ class QuillController extends ChangeNotifier { /// Returns all styles and Embed for each node within selection List getAllIndividualSelectionStylesAndEmbed() { final stylesAndEmbed = document.collectAllIndividualStyleAndEmbed( - selection.start, selection.end - selection.start); + selection.start, + selection.end - selection.start, + ); return stylesAndEmbed; } /// Returns plain text for each node within selection String getPlainText() { - final text = - document.getPlainText(selection.start, selection.end - selection.start); + final text = document.getPlainText( + selection.start, + selection.end - selection.start, + ); return text; } /// Returns all styles for any character within the specified text range. List