From fc1cb041b01e3397a772a4131ccbd9fa3b6c1ef0 Mon Sep 17 00:00:00 2001 From: zmtzawqlp Date: Thu, 11 May 2023 01:01:01 +0800 Subject: [PATCH] * Migrate to Flutter 3.10.0 and Dart 3.0.0 (#557,#563,#570,#572,#573) * Cherry Pick https://github.com/flutter/flutter/pull/110131 * Cherry Pick https://github.com/flutter/flutter/pull/119495 --- CHANGELOG.md | 10 + analysis_options.yaml | 5 - example/.metadata | 41 +- example/analysis_options.yaml | 5 +- .../android/app/src/debug/AndroidManifest.xml | 7 + .../fluttercandies/example/MainActivity.kt | 6 + .../app/src/profile/AndroidManifest.xml | 7 + example/android/build.gradle | 2 +- example/ios/.gitignore | 2 + example/ios/Flutter/.last_build_id | 1 - example/ios/Flutter/AppFrameworkInfo.plist | 2 +- example/ios/Flutter/Debug.xcconfig | 2 +- example/ios/Flutter/Release.xcconfig | 2 +- example/ios/Podfile | 5 +- example/ios/Runner.xcodeproj/project.pbxproj | 266 ++++++-- .../xcshareddata/xcschemes/Runner.xcscheme | 19 +- .../AppIcon.appiconset/Icon-App-20x20@1x.png | Bin 564 -> 295 bytes .../AppIcon.appiconset/Icon-App-20x20@2x.png | Bin 1283 -> 406 bytes .../AppIcon.appiconset/Icon-App-20x20@3x.png | Bin 1588 -> 450 bytes .../AppIcon.appiconset/Icon-App-29x29@1x.png | Bin 1025 -> 282 bytes .../AppIcon.appiconset/Icon-App-29x29@2x.png | Bin 1716 -> 462 bytes .../AppIcon.appiconset/Icon-App-29x29@3x.png | Bin 1920 -> 704 bytes .../AppIcon.appiconset/Icon-App-40x40@1x.png | Bin 1283 -> 406 bytes .../AppIcon.appiconset/Icon-App-40x40@2x.png | Bin 1895 -> 586 bytes .../AppIcon.appiconset/Icon-App-40x40@3x.png | Bin 2665 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@2x.png | Bin 2665 -> 862 bytes .../AppIcon.appiconset/Icon-App-60x60@3x.png | Bin 3831 -> 1674 bytes .../AppIcon.appiconset/Icon-App-76x76@1x.png | Bin 1888 -> 762 bytes .../AppIcon.appiconset/Icon-App-76x76@2x.png | Bin 3294 -> 1226 bytes .../Icon-App-83.5x83.5@2x.png | Bin 3612 -> 1418 bytes example/ios/Runner/Info.plist | 12 +- example/ios/RunnerTests/RunnerTests.swift | 12 + example/lib/common/text/at_text.dart | 110 ++-- example/lib/common/text/dollar_text.dart | 78 +-- example/lib/common/text/emoji_text.dart | 106 ++-- .../my_extended_text_selection_controls.dart | 574 +++++++++--------- .../text/my_special_text_span_builder.dart | 68 +-- .../lib/common/widget/memory_usage_chart.dart | 3 +- .../lib/common/widget/memory_usage_view.dart | 9 +- example/lib/common/widget/pic_swiper.dart | 140 ++--- example/lib/main.dart | 11 - .../lib/pages/complex/image_editor_demo.dart | 18 +- .../lib/pages/complex/photo_view_demo.dart | 125 ++-- example/pubspec.yaml | 39 +- example/web/icons/Icon-maskable-192.png | Bin 0 -> 5594 bytes example/web/icons/Icon-maskable-512.png | Bin 0 -> 20998 bytes lib/src/extended_image.dart | 5 +- lib/src/gesture/gesture.dart | 22 +- .../gesture/page_view/gesture_page_view.dart | 35 +- .../page_view/page_controller/official.dart | 369 +++++++++++ .../page_controller/page_controller.dart | 65 ++ .../page_controller/page_position.dart | 61 ++ .../page_view/widgets/page_controller.dart | 189 ------ .../page_view/widgets/scroll_position.dart | 234 ------- .../drag_gesture_recognizer.dart | 147 +++++ .../drag_gesture_recognizer_mixin.dart | 32 - .../{drag.dart => official.dart} | 559 +++++++++++------ .../gesture_detector/velocity_tracker.dart | 157 +---- .../velocity_tracker_mixin.dart | 27 - lib/src/image/painting.dart | 28 +- lib/src/utils.dart | 2 +- pubspec.yaml | 6 +- 62 files changed, 2041 insertions(+), 1584 deletions(-) create mode 100644 example/android/app/src/debug/AndroidManifest.xml create mode 100644 example/android/app/src/main/kotlin/com/fluttercandies/example/MainActivity.kt create mode 100644 example/android/app/src/profile/AndroidManifest.xml delete mode 100644 example/ios/Flutter/.last_build_id create mode 100644 example/ios/RunnerTests/RunnerTests.swift create mode 100644 example/web/icons/Icon-maskable-192.png create mode 100644 example/web/icons/Icon-maskable-512.png create mode 100644 lib/src/gesture/page_view/page_controller/official.dart create mode 100644 lib/src/gesture/page_view/page_controller/page_controller.dart create mode 100644 lib/src/gesture/page_view/page_controller/page_position.dart delete mode 100644 lib/src/gesture/page_view/widgets/page_controller.dart delete mode 100644 lib/src/gesture/page_view/widgets/scroll_position.dart create mode 100644 lib/src/gesture_detector/drag_gesture_recognizer.dart delete mode 100644 lib/src/gesture_detector/drag_gesture_recognizer_mixin.dart rename lib/src/gesture_detector/{drag.dart => official.dart} (63%) delete mode 100644 lib/src/gesture_detector/velocity_tracker_mixin.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a9d41a0..f2142c66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,13 @@ +## 8.0.1 + +* Dart sdk: '>=2.18.0 <4.0.0' + +## 8.0.0 + +* Migrate to Flutter 3.10.0 and Dart 3.0.0 (#557,#563,#570,#572,#573) +* Cherry Pick https://github.com/flutter/flutter/pull/110131 +* Cherry Pick https://github.com/flutter/flutter/pull/119495 + ## 7.0.2 * publish v6.4.1 for flutter 3.3.0 and v6.2.2 for flutter 3.0.5 diff --git a/analysis_options.yaml b/analysis_options.yaml index d8dbf79f..52b92671 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -19,9 +19,6 @@ # Android Studio, and the `flutter analyze` command. analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning @@ -139,10 +136,8 @@ linter: # - prefer_constructors_over_static_methods # not yet tested - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - prefer_final_fields - - prefer_final_in_for_each - prefer_final_locals - prefer_for_elements_to_map_fromIterable - prefer_foreach diff --git a/example/.metadata b/example/.metadata index f06a1f48..e94891d5 100644 --- a/example/.metadata +++ b/example/.metadata @@ -1,10 +1,45 @@ # This file tracks properties of this Flutter project. # Used by Flutter tool to assess capabilities and perform upgrades etc. # -# This file should be version controlled and should not be manually edited. +# This file should be version controlled. version: - revision: 63b2daff7f91afeaac47f3646f59eefd59210c41 - channel: unknown + revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + channel: stable project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: android + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: ios + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: linux + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: macos + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: web + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + - platform: windows + create_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + base_revision: 84a1e904f44f9b0e9c4510138010edcc653163f8 + + # User provided section + + # List of Local paths (relative to this file) that should be + # ignored by the migrate tool. + # + # Files that are not part of the templates will be ignored by default. + unmanaged_files: + - 'lib/main.dart' + - 'ios/Runner.xcodeproj/project.pbxproj' diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml index 7bafee68..f438fcd6 100644 --- a/example/analysis_options.yaml +++ b/example/analysis_options.yaml @@ -19,9 +19,6 @@ # Android Studio, and the `flutter analyze` command. analyzer: - strong-mode: - implicit-casts: false - implicit-dynamic: false errors: # treat missing required parameters as a warning (not a hint) missing_required_param: warning @@ -141,7 +138,7 @@ linter: # - prefer_constructors_over_static_methods # not yet tested - prefer_contains # - prefer_double_quotes # opposite of prefer_single_quotes - - prefer_equal_for_default_values + # - prefer_equal_for_default_values # - prefer_expression_function_bodies # conflicts with https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#consider-using--for-short-functions-and-methods - prefer_final_fields - prefer_final_in_for_each diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/app/src/main/kotlin/com/fluttercandies/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/fluttercandies/example/MainActivity.kt new file mode 100644 index 00000000..00b9cec2 --- /dev/null +++ b/example/android/app/src/main/kotlin/com/fluttercandies/example/MainActivity.kt @@ -0,0 +1,6 @@ +package com.fluttercandies.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity: FlutterActivity() { +} diff --git a/example/android/app/src/profile/AndroidManifest.xml b/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 00000000..399f6981 --- /dev/null +++ b/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/example/android/build.gradle b/example/android/build.gradle index ea1fb828..5480a15b 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -27,6 +27,6 @@ subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir } diff --git a/example/ios/.gitignore b/example/ios/.gitignore index e96ef602..7a7f9873 100644 --- a/example/ios/.gitignore +++ b/example/ios/.gitignore @@ -1,3 +1,4 @@ +**/dgph *.mode1v3 *.mode2v3 *.moved-aside @@ -18,6 +19,7 @@ Flutter/App.framework Flutter/Flutter.framework Flutter/Flutter.podspec Flutter/Generated.xcconfig +Flutter/ephemeral/ Flutter/app.flx Flutter/app.zip Flutter/flutter_assets/ diff --git a/example/ios/Flutter/.last_build_id b/example/ios/Flutter/.last_build_id deleted file mode 100644 index 9a7880a9..00000000 --- a/example/ios/Flutter/.last_build_id +++ /dev/null @@ -1 +0,0 @@ -1ee731e3db0f1684eabd1576c09e1bd5 \ No newline at end of file diff --git a/example/ios/Flutter/AppFrameworkInfo.plist b/example/ios/Flutter/AppFrameworkInfo.plist index 4f8d4d24..9625e105 100644 --- a/example/ios/Flutter/AppFrameworkInfo.plist +++ b/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - $(DEVELOPMENT_LANGUAGE) + en CFBundleExecutable App CFBundleIdentifier diff --git a/example/ios/Flutter/Debug.xcconfig b/example/ios/Flutter/Debug.xcconfig index e8efba11..ec97fc6f 100644 --- a/example/ios/Flutter/Debug.xcconfig +++ b/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#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 399e9340..c4855bfe 100644 --- a/example/ios/Flutter/Release.xcconfig +++ b/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" #include "Generated.xcconfig" diff --git a/example/ios/Podfile b/example/ios/Podfile index 093ca345..2c6a9b82 100644 --- a/example/ios/Podfile +++ b/example/ios/Podfile @@ -1,5 +1,5 @@ # Uncomment this line to define a global platform for your project - platform :ios, '9.1' +platform :ios, '11.0' # CocoaPods analytics sends network stats synchronously affecting flutter build latency. ENV['COCOAPODS_DISABLE_STATS'] = 'true' @@ -32,6 +32,9 @@ target 'Runner' do 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| diff --git a/example/ios/Runner.xcodeproj/project.pbxproj b/example/ios/Runner.xcodeproj/project.pbxproj index a1ac90e8..e1947927 100644 --- a/example/ios/Runner.xcodeproj/project.pbxproj +++ b/example/ios/Runner.xcodeproj/project.pbxproj @@ -7,15 +7,27 @@ objects = { /* Begin PBXBuildFile section */ + 00CE350D9CDC39AF135D10C4 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1C1A4391310D5B80A044529A /* Pods_RunnerTests.framework */; }; + 0A741DABE762BFBDBFD15B60 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A89B8B9AFAA42CACE4FDEBBD /* Pods_Runner.framework */; }; 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 */; }; 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 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 */; }; - 9B6B99532F6B6B0D644D8510 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 04A9794512BCC575968B0FE8 /* Pods_Runner.framework */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 331C8085294A63A400263BE5 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 97C146E61CF9000F007C117D /* Project object */; + proxyType = 1; + remoteGlobalIDString = 97C146ED1CF9000F007C117D; + remoteInfo = Runner; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXCopyFilesBuildPhase section */ 9705A1C41CF9048500538489 /* Embed Frameworks */ = { isa = PBXCopyFilesBuildPhase; @@ -30,10 +42,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 04A9794512BCC575968B0FE8 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 0913CD44C8612EAEA341C3BF /* 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 = ""; }; 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 = ""; }; + 1C1A4391310D5B80A044529A /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 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; }; + 33398257447959A9F2747E92 /* 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 = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; + 6B3F736569180CF18CEDD993 /* 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 = ""; }; @@ -44,9 +61,10 @@ 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 = ""; }; - A2E3B06F8BD464073FBD3A85 /* 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 = ""; }; - AEBFBC047BBB84F0F7577710 /* 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 = ""; }; - F5353890E7DA0DEC049ED2A8 /* 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 = ""; }; + 9A48409AB147FF74A0553EC9 /* 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 = ""; }; + A89B8B9AFAA42CACE4FDEBBD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + BDE2EADABA0BA7EF1ED4AA8E /* 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 = ""; }; + BE9D07E9BA17D31381DD6952 /* 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -54,29 +72,41 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9B6B99532F6B6B0D644D8510 /* Pods_Runner.framework in Frameworks */, + 0A741DABE762BFBDBFD15B60 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + FF289DFC2412B6C24DBA6AC1 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 00CE350D9CDC39AF135D10C4 /* Pods_RunnerTests.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 3CDA07FC6A06044EBF54B3B2 /* Pods */ = { + 171C9DBC427CED8F7C2A845D /* Pods */ = { isa = PBXGroup; children = ( - A2E3B06F8BD464073FBD3A85 /* Pods-Runner.debug.xcconfig */, - AEBFBC047BBB84F0F7577710 /* Pods-Runner.release.xcconfig */, - F5353890E7DA0DEC049ED2A8 /* Pods-Runner.profile.xcconfig */, - ); + 0913CD44C8612EAEA341C3BF /* Pods-Runner.debug.xcconfig */, + 6B3F736569180CF18CEDD993 /* Pods-Runner.release.xcconfig */, + 33398257447959A9F2747E92 /* Pods-Runner.profile.xcconfig */, + BDE2EADABA0BA7EF1ED4AA8E /* Pods-RunnerTests.debug.xcconfig */, + BE9D07E9BA17D31381DD6952 /* Pods-RunnerTests.release.xcconfig */, + 9A48409AB147FF74A0553EC9 /* Pods-RunnerTests.profile.xcconfig */, + ); + name = Pods; path = Pods; sourceTree = ""; }; - 6130C524CBAA69E77E76D577 /* Frameworks */ = { + 331C8082294A63A400263BE5 /* RunnerTests */ = { isa = PBXGroup; children = ( - 04A9794512BCC575968B0FE8 /* Pods_Runner.framework */, + 331C807B294A618700263BE5 /* RunnerTests.swift */, ); - name = Frameworks; + path = RunnerTests; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -96,8 +126,9 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 3CDA07FC6A06044EBF54B3B2 /* Pods */, - 6130C524CBAA69E77E76D577 /* Frameworks */, + 331C8082294A63A400263BE5 /* RunnerTests */, + 171C9DBC427CED8F7C2A845D /* Pods */, + F9BCA3697581ED7B1106B078 /* Frameworks */, ); sourceTree = ""; }; @@ -105,6 +136,7 @@ isa = PBXGroup; children = ( 97C146EE1CF9000F007C117D /* Runner.app */, + 331C8081294A63A400263BE5 /* RunnerTests.xctest */, ); name = Products; sourceTree = ""; @@ -124,21 +156,49 @@ path = Runner; sourceTree = ""; }; + F9BCA3697581ED7B1106B078 /* Frameworks */ = { + isa = PBXGroup; + children = ( + A89B8B9AFAA42CACE4FDEBBD /* Pods_Runner.framework */, + 1C1A4391310D5B80A044529A /* Pods_RunnerTests.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ + 331C8080294A63A400263BE5 /* RunnerTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */; + buildPhases = ( + 965F58E44DAB1CAB34993261 /* [CP] Check Pods Manifest.lock */, + 331C807D294A63A400263BE5 /* Sources */, + 331C807F294A63A400263BE5 /* Resources */, + FF289DFC2412B6C24DBA6AC1 /* Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 331C8086294A63A400263BE5 /* PBXTargetDependency */, + ); + name = RunnerTests; + productName = RunnerTests; + productReference = 331C8081294A63A400263BE5 /* RunnerTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; 97C146ED1CF9000F007C117D /* Runner */ = { isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - 0BF4781BAA765831BD6AA06E /* [CP] Check Pods Manifest.lock */, + 2812CC32DBD154B055629B43 /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, - AB33336E8A077DE07115D6D1 /* [CP] Embed Pods Frameworks */, + 3775EC5A021C67D963D3D265 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -158,6 +218,10 @@ LastUpgradeCheck = 1300; ORGANIZATIONNAME = ""; TargetAttributes = { + 331C8080294A63A400263BE5 = { + CreatedOnToolsVersion = 14.0; + TestTargetID = 97C146ED1CF9000F007C117D; + }; 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; LastSwiftMigration = 1100; @@ -178,11 +242,19 @@ projectRoot = ""; targets = ( 97C146ED1CF9000F007C117D /* Runner */, + 331C8080294A63A400263BE5 /* RunnerTests */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + 331C807F294A63A400263BE5 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EC1CF9000F007C117D /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -197,7 +269,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 0BF4781BAA765831BD6AA06E /* [CP] Check Pods Manifest.lock */ = { + 2812CC32DBD154B055629B43 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -219,6 +291,23 @@ 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; }; + 3775EC5A021C67D963D3D265 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; alwaysOutOfDate = 1; @@ -226,6 +315,7 @@ files = ( ); inputPaths = ( + "${TARGET_BUILD_DIR}/${INFOPLIST_PATH}", ); name = "Thin Binary"; outputPaths = ( @@ -234,41 +324,54 @@ shellPath = /bin/sh; shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 965F58E44DAB1CAB34993261 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; - alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Run Script"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + 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; }; - AB33336E8A077DE07115D6D1 /* [CP] Embed Pods Frameworks */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; buildActionMask = 2147483647; files = ( ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", + inputPaths = ( ); - name = "[CP] Embed Pods Frameworks"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", + name = "Run Script"; + outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + 331C807D294A63A400263BE5 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 97C146EA1CF9000F007C117D /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -280,6 +383,14 @@ }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 331C8086294A63A400263BE5 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 97C146ED1CF9000F007C117D /* Runner */; + targetProxy = 331C8085294A63A400263BE5 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin PBXVariantGroup section */ 97C146FA1CF9000F007C117D /* Main.storyboard */ = { isa = PBXVariantGroup; @@ -357,22 +468,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 3CJ9HMC222; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.extendedImageExample; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -380,6 +482,56 @@ }; name = Profile; }; + 331C8088294A63A400263BE5 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BDE2EADABA0BA7EF1ED4AA8E /* Pods-RunnerTests.debug.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Debug; + }; + 331C8089294A63A400263BE5 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = BE9D07E9BA17D31381DD6952 /* Pods-RunnerTests.release.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Release; + }; + 331C808A294A63A400263BE5 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9A48409AB147FF74A0553EC9 /* Pods-RunnerTests.profile.xcconfig */; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example.RunnerTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 5.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Runner.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/Runner"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -494,22 +646,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 3CJ9HMC222; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.extendedImageExample; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -525,22 +668,13 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; CLANG_ENABLE_MODULES = YES; CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; - DEVELOPMENT_TEAM = 3CJ9HMC222; ENABLE_BITCODE = NO; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); INFOPLIST_FILE = Runner/Info.plist; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - LIBRARY_SEARCH_PATHS = ( - "$(inherited)", - "$(PROJECT_DIR)/Flutter", - ); - PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.extendedImageExample; + PRODUCT_BUNDLE_IDENTIFIER = com.fluttercandies.example; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; SWIFT_VERSION = 5.0; @@ -551,6 +685,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 331C8088294A63A400263BE5 /* Debug */, + 331C8089294A63A400263BE5 /* Release */, + 331C808A294A63A400263BE5 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 3db53b6e..e42adcb3 100644 --- a/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -27,8 +27,6 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + + + + + - - Gw7hsN~k)CYt4dQDFxbs5*_&e@Hj)wtt(&JE<3Eq*D z;_gQLvqXoKv=I*gWqM9C(Tvu0>=?hTbOp9!6k6AF;>f6|S5%jGEE}TA9h)e`Yuiu8 d7)l?o1NFcJg%EAfM$P~L002ovPDHLkV1g^Dnv?(l delta 550 zcmV+>0@?ki0<;8>8Gi-<0051N9Sr~g00DDSM?wIu&K&6g00HhvL_t(I5v`QFOB_)Y z#?QI;j_a;jjf#Z$YJ7mH(xecJU?W)A`9CN~KrBV85C}GDQ=|;GDFPNjtWty!L{u=? zh>8yo%^GE+J9o~_IZFoiamQVQXP7%LzTbT3F@uf+9x&7cvVV%GdjTaC;zf>@mq<=3 z!c<%*UT)@yJ|0BK6~d4Jx-*KV`ZQ(@VyUPupum=XhInNG#Z_k-X|hK{B}~9IfiWx} zLD5QY6Vm)p0NrWymdkrHPN5Vgwd>5>4HI1=@PA+e^rq~CEj|n2X`??)0mUI*D{KBn zjv{V=y5X9|X@3grkpcXC6oou4ML~ezCc2EtnsQTB4tWNg?4bkf;hG7IMfhgNI(FV5 zGs4|*GyMTIY0$B=_*mso9+>eB z?J{?+FLkYu+4_Uk`r_>LHF~flZm0oBf#vr8%vJ>#p~!KNvqGG3)|f1T_)ydeh8$vDceZ>oNbH^|*hJ*t?Yc*1`WB&W>VYVEzu) zq#7;;VjO)t*nbgf(!`OXJBr45rP>>AQr$6c7slJWvbpNW@KTwna6d?PP>hvXCcp=4 zF;=GR@R4E7{4VU^0p4F>v^#A|>07*qoM6N<$f<+$JJpcdz delta 1274 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 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png index 4cde12118dda48d71e01fcb589a74d069c5d7cb5..4cd7b0099ca80c806f8fe495613e8d6c69460d76 100644 GIT binary patch delta 266 zcmV+l0rmcY2$}+r8Gi!+003c4mpuRg09{Z_R7L;)|5U~JDYo_jSDX9(|7FYh`2GLd z^Zv2r{H^2sT*&w!Y^SB+`<>qVZqE6)=lqo0`vF#&*75!I`TIh@_d&k*HoEtQyV-iD z%Xz2D9EQRbeYh5Nr~y=#0ZD;^+vz0$004MNL_t(2&&|%+4u6C&2tZM$Wf&dzefR%A z(^3-?6X>hnCz2Ba@RH&`m!pgy?n@#@AuLYB&}Q)FGY`?vcft0!vht0Z@M&ZeNCWXh75gzRTXR8EE3oN&6 Q00000NkvXXt^-0~g5kS`djJ3c delta 1014 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 Zxpx7IC@PekH?;r&002ovPDHLkV1i)CYaajr delta 1916 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 delta 1274 zcmV@pi1MCNO0zH7s z{8#}P0)7Ba8DqYf&QgSne>X__O83t$NZM4&R0{XJq|x}oAU?tcfC@|eNz$04T}34& z8DJf78R&>*Zz`k$q{`#gfGHnx7nlH^G{y`jfER)1<_fNi<9aM%_zrm1C`yPkKma(+ ztQ;y*CR2bbBYz>zG*SVsfpkGU(q>uHZf3iogk_%#9E|5SWeHrmAo>P;ejX7mwq#*} zW25m^ZI+{(Z8fI?4jM_fffY0nok=+88^|*_DwcW>mR#e+X$F_KMdb6sRz!~7K zkyN0G(3XQ+;z3X%PZ4gh;n-%62U<*VUKNv(D&IDi_4_D!s#MVXp|-XhH;H z#&@_;oApJVd}}5O@b=X_gJboD^-fM@6|#V@sA%X)Rlkd}3MLH0dGXGG&-HX|aD~|M zC)W#H7=H?AbtdaV#dGpubj_O^J-SlWpVNv-5(;wR%mvE9`Qaqo>03b&##eNNf=m#B z9@^lsd8tJ;BvI86kNV zc~0CY(7V{s+h%cWG|y=gt|q`z$l<(@qU=i?9q#uz`G?PgDMK!VMGidHZt*N+1L0ZI zFkH=mFtywc6rJ}C_?)=m)18V!ZQ`*-j(D`gCFK|nt#{bk*%%zuQ7o7kvJgA^=(^7b zzkm5GZ;jxRn{Wup8IOUx8D4uh&(=Ox-7$a;U><*5L^!% zxRlw)vAbh;sdlR||&e}8_8%)c2Fwy=F& zH|LM+p{pZB5DKTx>Y?F1N%BlZkXf!}Jb#viX>Oi;kBKp1x_fc0#UIbIeSJ^EkWFox zijdim{ojmn@#7EC*aY;fC0W*WN+DmQtE06pNK3SfZ^#@2K`6RgEuU_KwJTQ>E?Yar zc_9e#I$F8%>kuy-JI6ocSsYvQGbsxUCx04(w1z-pMRz9`kH5smmF@WHEG?dcYkv){ zV?kn3XB$_3zr*h1Uow)(<5)w5;3Wh1jHI)`ZlXp&!yEV{Y_~@;?CLwq;4eeaGOe6( zEsSSbwSGD0-`dUUl014$1_O8Gi!+006nq0-pc?0H{z*R7L;)|5U~JDYo_jSDXF*|5nEMy6F5^ z$M}8I`uzU?*Yf=uXr;5|{0m;6_Wb|A>ik^D_|)+I$?g3CSDK^3+eX0mD!2CP`2NN0 z{dLg!a?km&%iyTt`yiax0acdp`~T(l{$a`ZF1YpsRg(cvjDG_-U$Er-fz#Bw>2W$eUI#iU z)Wdgs8Y3U+A$Gd&{+j)d)BmGKx+43U_!tik_YlN)>$7G!hkE!s;%oku3;IwG3U^2k zw?z+HM)jB{@zFhK8P#KMSytSthr+4!c(5c%+^UBn_j%}l|2+O?a>_7qq7W zmx(qtA2nV^tZlLpy_#$U%ZNx5;$`0L&dZ!@e7rFXPGAOup%q`|03hpdtXsPP0000< KMNUMnLSTZ1N;Pr- delta 1891 zcmV-p2b}oI1m_Nr8Gi-<0052=@~r>>2QEoOK~#9!?VW3E6jc<*XLh$yKNt;)Mial3 z7z%<>zxaV5DhMs*(b6YIW1=KP6Jj(m21QYbiJ}su&;o5EN=$%gptMj6p|(7#AOTUJ zlt8fsX(iGq?ZQ50=XmbU+~w|cmz~|6$KBbz$-g^IcV>Hk`+q<8%-p?uMi3G-0B~!5 ze-yPCwFPw?HGmpMc~K)7BCq;C528+>zC*o^8h^XKC)IFgkv#xzm!ewK7j|kRa9dFo zC>MoDSR@P2#cWSU{i1oH5K2-Xb3jRz>|h7VOh0K` zhq^--L3H}A0r)nr z;Tr|-kPjB1s=ItpnS`oT%|U=a4oK-ZFIE^YBLH{u2#~@%%D^K)$`9*Tg(~9M-B+Zj z;~H?4LVsEt0eFtN4&>H(DZ@KpI6RhBKLL21CxC`J&m4Gc^9wwMZU#7SR1+KtuhSZM z+yLY}Vekzw6T_ApfEkuB_yU;e&a)L@rX~z70A_N+upOXN!qygmPDmKG0d%7CECcAI zgkd>ArzH$a0XjKsO$X@IgkcH5Y;m3`0G*yNOn(KK4GF_EfL4aB5i1j9o&Z{vFk~k> z&?@K2jQcJO%W!cddG(_DyfSoO55bUMHtbDF8DPkwF^~Ql#Eq4w15k{h%ML5Ar&pzi zl-D7v8kQXQ!&RRgKCW#5DZB$$6?mjWm50rRw*ukK>P-GkA|k69h{NARc>e}uLx+U4 z0DqE>7pa}9Fez+Vc-3jb`%i^uulglFoMzAVR|2%rf= zf#;74FXF^Ku_4+G&-4$KVy%YP>%2rxu2VG_cdm?XRjEhF&wPXJ># z_Q2+jGs=l~Fyx#MmGn+PZ0`@kBfGp|fO;Vov<$;z`(+sSZ7;Y=zXaF(8rb@CuQDV^ zq3i(2LfqO%AS!Ss>V%j7%>{6mtbYQrtQK5V4InPq0NZSaXv+f2U=&2}Z6OvkBfNHi z{LSaVJ!d5dC2K*ft_L^DRk;boQhOoVw!~Kt#0b2vd%!(&DF|~u1F@nG#LA5zR&7Fv z4GKgXooMSKb1g)6Obo-rgpuEP20T;W0Aa>55KC4gtQrKkAq-Hgs@FigV1GG8+rQ=z z6Jm=Bui-SfpDYLA=|vzGE(dYm=OC8XM&MDo7ux4UF1~0J1+i%aCUpRet3L_uNyQ*c zE(38Uy03H%I*)*Bh=Lb^Xj3?I^Hnbeq72(EOK^Y93CNp*uAA{5Lc=kyx=~RKa4{iT zm{_>_vSCm?$Ej=i6@=m%@PE9t1zZaoM}@2|h!#1K02~31S_I<0ZV=|K0}n!RRX6Ac zXmMf*5P-dLW}WPVsCKq)-x(0*txpZ2xrv3cxJ%l=7lpoNCyG< zK92ySAcmb-3m&}s@VwXv9(0#p<>B-5$bMxT;rk;OmENa6eM4D&LVo~01soUL39?R{ zyFLt3m|v?rCK7#KNu9E9Q4KV-pEUv^{rrClE&X&9I4-e7%pu_31#zGTOfC=ab%w20R*zBP+uT#l2{a~~~0wuG%6 zco*tVxK&e>%SJj*K!2tq*_h&ES5S9@TKb8WzpK;`&b9dNdxh4S)z%Q)o`aYWUh}9L z(`p!#WO5IxI|nf?yz{90R93Ed6@2qim*}Zjj$H&Esd`?JsFJUnDfiAgF_eYiWR3GC z>M9SHDylEWrA(%mfm~;u7OU9!Wz^!7Z%jZF zi@JR;>Mhi7S>V7wQ176|FdW2m?&`qa(ScO^CFPR80HucLHOTy%5s*HR0^8)i0WYBP d*#0Ks^FNSabJA*5${_#%002ovPDHLkV1gB0Vle;! diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png index a6d6b8609df07bf62e5100a53a01510388bd2b22..0ec303439225b78712f49115768196d8d76f6790 100644 GIT binary patch delta 850 zcmV-Y1Fih&6y64q8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f@rA97ytkO literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png index a6d6b8609df07bf62e5100a53a01510388bd2b22..0ec303439225b78712f49115768196d8d76f6790 100644 GIT binary patch delta 850 zcmV-Y1Fih&6y64q8Gi!+000iU#^3+|0OwFlR7L;)|5U~J09TtSw)Xt~|5(QO`~Ck( z!T0|D|3<*~RmJ%E{r+;#`2ba!klFf7!uJMSo%Q?vP{jByxcAZE>;OrUCbaZYjJo^$ z{nGILmD~Da$@upC{`C6(Ey4dPw)Pyc^>5DkHoEo!QcuK-Jwl-l}t(fQKv z{dds$V#@dygS`PvhX6is7Z+@*x-d;$ zb=6f@U3Jw}_s+W3%*+b9H_vS)-R#9?zrXogeLVI2We2RFTTAL}&3C8PS~<5D&v@UI z+`s*$wqQ=yd$laNUY-|ovcS9~n_90tFUdl#qq0tEUXle|k{Op|DHpSrbxEeZ5~$>o%>OSe z^=41qvh3LlC2xXzu+-2eQoqs1^L>7ylB$bCP);(%(xYZL1 cY5!B-0ft0f?Lgb>C;$Ke07*qoM6N<$f@rA97ytkO literal 2665 zcmV-v3YPVWP)oFh3q0MFesq&64WThn3$;G69TfjsAv=f2G9}p zgSx99+!YV6qME!>9MD13x)k(+XE7W?_O4LoLb5ND8 zaV{9+P@>42xDfRiYBMSgD$0!vssptcb;&?u9u(LLBKmkZ>RMD=kvD3h`sk6!QYtBa ztlZI#nu$8lJ^q2Z79UTgZe>BU73(Aospiq+?SdMt8lDZ;*?@tyWVZVS_Q7S&*tJaiRlJ z+aSMOmbg3@h5}v;A*c8SbqM3icg-`Cnwl;7Ts%A1RkNIp+Txl-Ckkvg4oxrqGA5ewEgYqwtECD<_3Egu)xGllKt&J8g&+=ac@Jq4-?w6M3b*>w5 z69N3O%=I^6&UL5gZ!}trC7bUj*12xLdkNs~Bz4QdJJ*UDZox2UGR}SNg@lmOvhCc~ z*f_UeXv(=#I#*7>VZx2ObEN~UoGUTl=-@)E;YtCRZ>SVp$p9yG5hEFZ!`wI!spd)n zSk+vK0Vin7FL{7f&6OB%f;SH22dtbcF<|9fi2Fp%q4kxL!b1#l^)8dUwJ zwEf{(wJj@8iYDVnKB`eSU+;ml-t2`@%_)0jDM`+a46xhDbBj2+&Ih>1A>6aky#(-SYyE{R3f#y57wfLs z6w1p~$bp;6!9DX$M+J~S@D6vJAaElETnsX4h9a5tvPhC3L@qB~bOzkL@^z0k_hS{T4PF*TDrgdXp+dzsE? z>V|VR035Pl9n5&-RePFdS{7KAr2vPOqR9=M$vXA1Yy5>w;EsF`;OK{2pkn-kpp9Pw z)r;5JfJKKaT$4qCb{TaXHjb$QA{y0EYy*+b1XI;6Ah- zw13P)xT`>~eFoJC!>{2XL(a_#upp3gaR1#5+L(Jmzp4TBnx{~WHedpJ1ch8JFk~Sw z>F+gN+i+VD?gMXwcIhn8rz`>e>J^TI3E-MW>f}6R-pL}>WMOa0k#jN+`RyUVUC;#D zg|~oS^$6%wpF{^Qr+}X>0PKcr3Fc&>Z>uv@C);pwDs@2bZWhYP!rvGx?_|q{d`t<*XEb#=aOb=N+L@CVBGqImZf&+a zCQEa3$~@#kC);pasdG=f6tuIi0PO-y&tvX%>Mv=oY3U$nD zJ#gMegnQ46pq+3r=;zmgcG+zRc9D~c>z+jo9&D+`E6$LmyFqlmCYw;-Zooma{sR@~ z)_^|YL1&&@|GXo*pivH7k!msl+$Sew3%XJnxajt0K%3M6Bd&YFNy9}tWG^aovK2eX z1aL1%7;KRDrA@eG-Wr6w+;*H_VD~qLiVI`{_;>o)k`{8xa3EJT1O_>#iy_?va0eR? zDV=N%;Zjb%Z2s$@O>w@iqt!I}tLjGk!=p`D23I}N4Be@$(|iSA zf3Ih7b<{zqpDB4WF_5X1(peKe+rASze%u8eKLn#KKXt;UZ+Adf$_TO+vTqshLLJ5c z52HucO=lrNVae5XWOLm!V@n-ObU11!b+DN<$RuU+YsrBq*lYT;?AwJpmNKniF0Q1< zJCo>Q$=v$@&y=sj6{r!Y&y&`0$-I}S!H_~pI&2H8Z1C|BX4VgZ^-! zje3-;x0PBD!M`v*J_)rL^+$<1VJhH*2Fi~aA7s&@_rUHYJ9zD=M%4AFQ`}k8OC$9s XsPq=LnkwKG00000NkvXXu0mjfhAk5^ diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png index 75b2d164a5a98e212cca15ea7bf2ab5de5108680..e9f5fea27c705180eb716271f41b582e76dcbd90 100644 GIT binary patch delta 1668 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 diff --git a/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png index c4df70d39da7941ef3f6dcb7f06a192d8dcb308d..84ac32ae7d989f82d5e46a60405adcc8279e8001 100644 GIT binary patch delta 749 zcmVg;Ps8|O$@u8^{Z_{KM!@$5TAfS6_e#O{MZfpz`2O`0$7~@NRr(1{THzH08y3x{{PYM{eL;T_A9^tcF_4Sxb`8l z_9V3RD6;a(-0A^Pjsi!1?)d#Ap4Tk3^CP0(07;VpJ7@tgQ}z4)*zx@&yZwC9`DV-b z0ZobH_5IB4{KxD3;p_6%|f=bdFhu+F!zMZ2UFj;GUKX7tI;hv3{q~!*pMj75WP_c}> z6)IWvg5_yyg<9Op()eD1hWC19M@?_9_MHec{Z8n3FMs~w_u?Av_yNBmRxVYrpi(M% zFMP21g+hmocQp3ay*Su=qM6He)*HaaTg$E^sym`(t%s3A)x!M+vfjXUBEpK6X9%iU zU!u9jj3(-$dM~sJ%Liy#?|+!6IY#MTau#O6vVj`yh_7%Ni!?!VS+MPTO(_fG+1<#p zqu;A#i+_(N%CmVnYvb>#nA{>Q%3E`Ds7<~jZMywn@h2t>G-LrYy7?Dj{aZqhQd6tzX%(Trn+ z)HNF}%-F{rr=m*0{=a;s#YDL00000NkvXXu0mjfaGjYE delta 1884 zcmV-i2c!7<1>g>l8Gi-<0076AQ7Zrd2Pa8HK~#9!?VNjT6h$1z_m0EFf5bmb1dTDK zp;kdKV1h(V(8Sc1M<37!RE>znAk{x4#zX@eOeE1j3~!+nB5IL z<xS}u?#DBMB>w^b($1Z)`9G?eP95EKi& z$eOy@K%h;ryrR3la%;>|o*>CgB(s>dDcNOXg}CK9SPmD?Uu$P4(=PGA0ShFasNfcIHTL?9WjB9#(2xSLC z`0%$#9DW9F;B4mbU{BlaYx!SjF!QSeF~(msQRxwboh5B_O$BWOQja)GboJz$&!?mgB&3$ytsA zvns&b3Cl5Hx#%p%faR*Q906u&fbXy$maV`n?S>A)vJIH!F-vxCrY+rq5_JA(GcOgu7(Ky4X3ATR9z8*%k&<5qYeV&4Y`~}XYmK(j{)!g8d2UgHXIINM!Rvn zKtEq~Foe0s!U{kux~F6Y7Sp+2f|*Cc${S{@oh8D0=XhB8Ec-w9CflfL+te4ium2cU zoPTCj_m<3d#gjK=<*8R`HP^C$lOPM5d~UhKhRRmvv{LI za^|oavk1$QiEApSrP@~Jjbg`<*dW4TO@DPEEX$Tg$xh?Y>Qd}y@kaH~IT8!lLpS^J zR7(&wZSI6+>Eb)tX>9Z?GX#q$u z4I>7e#b7ojyJ1grOh!^}s8S#ubi^Jkd1?UK)3mp6rI^_zxRY zrx6_QmhoWoDR`fp4R7gu6@OBFGu7IDVR6~nJsB{^f5jHn<{WJ&&f^X?3f8TIk3#U& zu1*Q-e@;snJxNx8-PBnpI|uFTKN!+Lp;fPfZ+eqqU^Y1|#DJY~126?zOx-+d>%4*? z&o`TbrXSNXZW^!P0t2>@$6&aiBtUDh2wLXLD9&a(1J=k_FK|iGbAQ@x4Qmx}Ms+*; zze&q6bH(=wYuXHfz0H6<05!LkE4rl~v^!bj=^9d+vI5fN<;GP>*Pas=q2l9RxDkk` zPRk&EQI+t_0$Y%nKE)Ma)W?jaA@4Z{h zTk*7;;#lG?hvTN_On=Jaxp%bdE;mDq(q#dgdYF|-?wrMeI4h`$idZ6^VyXZVlaCd0 z;i)OYR3npf@9>00Gqn##Zb4HRurgaWFCzL9u6@J@sse>Z1XznxWvSy%Td32I3!#YN zXt9v0)RQtDDZRd?#WY?~KF7A0UcR{jt9 W+;fr}hV%pg0000&=UXv0SHh`R7L;)|5U~JDYo_jSDRDC`1<|-SjPDL z{{Q{{{{H{}09Kk-#rR9Y_viNgVafPO!S|ls`uzR=MZfp^{QU=8od8La1X`Tr_Wmff z_5e$ivgQ1@=KMy$_g9a+`TPAle6cOJ_Fc#L7qIpvwDkd1mw$fK`6IOUD75rX!}mad zv(fMTE4=(Nx%L54lL1hVF1YpqNrC`FddBPg#_Ietx%Lrkq5wX00X1L{S%Cm9QY*av z#_Rh5PKy9KYTWbvz3BX9%J>0Hi1+#X{rLA{m%$Kamk?i!03AC38#Yrxs)5QTeTVRiEmz~MKK1WAjCw(c-JK6eox;2O)?`?TG`AHia671e^vgmp!llK zp|=5sVHk#C7=~epA~VAf-~%aPC=%Qw01h8mnSZ|p?tc*y?iZ$PR7_ceEIapF3KB14K0Pog?7wtd+^xgUCa_GVmlD z<^nU>AU_Yn-JU?NFdu|wf^bTCNf-wSBYVZltDdvGBln-YrbeGvJ!|s{#`gjN@yAMb zM6cjFz0eFECCsc|_8hTa3*9-JQGehksdoVP^K4m?&wpA~+|b%{EP5D-+7h)6CE; z*{>BP=GRR3Ea}xyV*bqry{l^J=0#DaC4ej;1qs8_by?H6Tr@7hl>UKNZt)^B&yl;)&oqzLg zcfZxpE?3k%_iTOVywh%`XVN-E#COl+($9{v(pqSQcrz=)>G!!3HeNxbXGM@})1|9g zG4*@(OBaMvY0P0_TfMFPh fVHk#CZX3S=^^2mI>Ux-D00000NkvXXu0mjfzK(<8 literal 3294 zcmV<43?cK0P)1^@s67{VYS000c7NklQEG_j zup^)eW&WUIApqy$=APz8jE@awGp)!bsTjDbrJO`$x^ZR^dr;>)LW>{ zs70vpsD38v)19rI=GNk1b(0?Js9~rjsQsu*K;@SD40RB-3^gKU-MYC7G!Bw{fZsqp zih4iIi;Hr_xZ033Iu{sQxLS=}yBXgLMn40d++>aQ0#%8D1EbGZp7+ z5=mK?t31BkVYbGOxE9`i748x`YgCMwL$qMsChbSGSE1`p{nSmadR zcQ#R)(?!~dmtD0+D2!K zR9%!Xp1oOJzm(vbLvT^$IKp@+W2=-}qTzTgVtQ!#Y7Gxz}stUIm<1;oBQ^Sh2X{F4ibaOOx;5ZGSNK z0maF^@(UtV$=p6DXLgRURwF95C=|U8?osGhgOED*b z7woJ_PWXBD>V-NjQAm{~T%sjyJ{5tn2f{G%?J!KRSrrGvQ1(^`YLA5B!~eycY(e5_ z*%aa{at13SxC(=7JT7$IQF~R3sy`Nn%EMv!$-8ZEAryB*yB1k&stni)=)8-ODo41g zkJu~roIgAih94tb=YsL%iH5@^b~kU9M-=aqgXIrbtxMpFy5mekFm#edF9z7RQ6V}R zBIhbXs~pMzt0VWy1Fi$^fh+1xxLDoK09&5&MJl(q#THjPm(0=z2H2Yfm^a&E)V+a5 zbi>08u;bJsDRUKR9(INSc7XyuWv(JsD+BB*0hS)FO&l&7MdViuur@-<-EHw>kHRGY zqoT}3fDv2-m{NhBG8X}+rgOEZ;amh*DqN?jEfQdqxdj08`Sr=C-KmT)qU1 z+9Cl)a1mgXxhQiHVB}l`m;-RpmKy?0*|yl?FXvJkFxuu!fKlcmz$kN(a}i*saM3nr z0!;a~_%Xqy24IxA2rz<+08=B-Q|2PT)O4;EaxP^6qixOv7-cRh?*T?zZU`{nIM-at zTKYWr9rJ=tppQ9I#Z#mLgINVB!pO-^FOcvFw6NhV0gztuO?g ztoA*C-52Q-Z-P#xB4HAY3KQVd%dz1S4PA3vHp0aa=zAO?FCt zC_GaTyVBg2F!bBr3U@Zy2iJgIAt>1sf$JWA9kh{;L+P*HfUBX1Zy{4MgNbDfBV_ly z!y#+753arsZUt@366jIC0klaC@ckuk!qu=pAyf7&QmiBUT^L1&tOHzsK)4n|pmrVT zs2($4=?s~VejTFHbFdDOwG;_58LkIj1Fh@{glkO#F1>a==ymJS$z;gdedT1zPx4Kj ztjS`y_C}%af-RtpehdQDt3a<=W5C4$)9W@QAse;WUry$WYmr51ml9lkeunUrE`-3e zmq1SgSOPNEE-Mf+AGJ$g0M;3@w!$Ej;hMh=v=I+Lpz^n%Pg^MgwyqOkNyu2c^of)C z1~ALor3}}+RiF*K4+4{(1%1j3pif1>sv0r^mTZ?5Jd-It!tfPfiG_p$AY*Vfak%FG z4z#;wLtw&E&?}w+eKG^=#jF7HQzr8rV0mY<1YAJ_uGz~$E13p?F^fPSzXSn$8UcI$ z8er9{5w5iv0qf8%70zV71T1IBB1N}R5Kp%NO0=5wJalZt8;xYp;b{1K) zHY>2wW-`Sl{=NpR%iu3(u6l&)rc%%cSA#aV7WCowfbFR4wcc{LQZv~o1u_`}EJA3>ki`?9CKYTA!rhO)if*zRdd}Kn zEPfYbhoVE~!FI_2YbC5qAj1kq;xP6%J8+?2PAs?`V3}nyFVD#sV3+uP`pi}{$l9U^ zSz}_M9f7RgnnRhaoIJgT8us!1aB&4!*vYF07Hp&}L zCRlop0oK4DL@ISz{2_BPlezc;xj2|I z23RlDNpi9LgTG_#(w%cMaS)%N`e>~1&a3<{Xy}>?WbF>OOLuO+j&hc^YohQ$4F&ze z+hwnro1puQjnKm;vFG~o>`kCeUIlkA-2tI?WBKCFLMBY=J{hpSsQ=PDtU$=duS_hq zHpymHt^uuV1q@uc4bFb{MdG*|VoW@15Osrqt2@8ll0qO=j*uOXn{M0UJX#SUztui9FN4)K3{9!y8PC-AHHvpVTU;x|-7P+taAtyglk#rjlH2 z5Gq8ik}BPaGiM{#Woyg;*&N9R2{J0V+WGB69cEtH7F?U~Kbi6ksi*`CFXsi931q7Y zGO82?whBhN%w1iDetv%~wM*Y;E^)@Vl?VDj-f*RX>{;o_=$fU!&KAXbuadYZ46Zbg z&6jMF=49$uL^73y;;N5jaHYv)BTyfh&`qVLYn?`o6BCA_z-0niZz=qPG!vonK3MW_ zo$V96zM!+kJRs{P-5-rQVse0VBH*n6A58)4uc&gfHMa{gIhV2fGf{st>E8sKyP-$8zp~wJX^A*@DI&-;8>gANXZj zU)R+Y)PB?=)a|Kj>8NXEu^S_h^7R`~Q&7*Kn!xyvzVv&^>?^iu;S~R2e-2fJx-oUb cX)(b1KSk$MOV07*qoM6N<$f&{Qds= z{r_0T`1}6fwc-8!#-TGX}_?g)CZq4{k!uZ_g@DrQdoW0kI zu+W69&uN^)W`CK&06mMNcYMVF00dG=L_t(|+U?wHQxh>12H+Dm+1+fh+IF>G0SjJM zkQQre1x4|G*Z==(Ot&kCnUrL4I(rf(ucITwmuHf^hXiJTkdTm&kdTm&kdTm&kdP`e zsgWG0BcWCVkVZ&2dUwN`cgM8QJb`Z7Z~e<&Yj2(}>VI$fQI%^ugM`#6By?GeadWcu z0gy9!D`m!H>Bd!JW(@avE8`|5XX(0PN}!8K>`dkavs;rHL+wy96QGNT=S@#7%xtlm zIW!++@*2zm-Py#Zr`DzqsLm!b{iskFNULSqE9A>SqHem>o31A%XL>S_5?=;V_i_y+ z(xxXhnt#r-l1Y8_*h`r?8Tr|)(RAiO)4jQR`13X0mx07C&p@KBP_2s``KEhv^|*8c z$$_T(v6^1Ig=#R}sE{vjA?ErGDZGUsyoJuWdJMc7Nb1^KF)-u<7q zPy$=;)0>vuWuK2hQhswLf!9yg`88u&eBbR8uhod?Nw09AXH}-#qOLLxeT2%C;R)QQ$Za#qp~cM&YVmS4i-*Fpd!cC zBXc?(4wcg>sHmXGd^VdE<5QX{Kyz$;$sCPl(_*-P2Iw?p^C6J2ZC!+UppiK6&y3Kmbv&O!oYF34$0Z;QO!J zOY#!`qyGH<3Pd}Pt@q*A0V=3SVtWKRR8d8Z&@)3qLPA19LPA19LPEUCUoZo%k(yku QW&i*H07*qoM6N<$g47z!?*IS* literal 3612 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 iW8 CFBundleDevelopmentRegion $(DEVELOPMENT_LANGUAGE) + CFBundleDisplayName + Example CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -11,7 +13,7 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - extended_image_example + example CFBundlePackageType APPL CFBundleShortVersionString @@ -41,15 +43,11 @@ UIViewControllerBasedStatusBarAppearance - NSPhotoLibraryUsageDescription - Read your photos for display - NSCameraUsageDescription - Take a photo for display - NSMicrophoneUsageDescription - Take a video for display CADisableMinimumFrameDurationOnPhone UIApplicationSupportsIndirectInputEvents + FLTEnableImpeller + diff --git a/example/ios/RunnerTests/RunnerTests.swift b/example/ios/RunnerTests/RunnerTests.swift new file mode 100644 index 00000000..86a7c3b1 --- /dev/null +++ b/example/ios/RunnerTests/RunnerTests.swift @@ -0,0 +1,12 @@ +import Flutter +import UIKit +import XCTest + +class RunnerTests: XCTestCase { + + func testExample() { + // If you add code to the Runner application, consider adding tests here. + // See https://developer.apple.com/documentation/xctest for more information about using XCTest. + } + +} diff --git a/example/lib/common/text/at_text.dart b/example/lib/common/text/at_text.dart index d80ab3e3..4382be05 100644 --- a/example/lib/common/text/at_text.dart +++ b/example/lib/common/text/at_text.dart @@ -1,62 +1,62 @@ -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; +// import 'package:extended_text_library/extended_text_library.dart'; +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; -class AtText extends SpecialText { - AtText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, - {this.showAtBackground = false, this.start}) - : super(flag, ' ', textStyle, onTap: onTap); - static const String flag = '@'; - final int? start; +// class AtText extends SpecialText { +// AtText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, +// {this.showAtBackground = false, this.start}) +// : super(flag, ' ', textStyle, onTap: onTap); +// static const String flag = '@'; +// final int? start; - /// whether show background for @somebody - final bool showAtBackground; +// /// whether show background for @somebody +// final bool showAtBackground; - @override - InlineSpan finishText() { - final TextStyle? textStyle = - this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); +// @override +// InlineSpan finishText() { +// final TextStyle? textStyle = +// this.textStyle?.copyWith(color: Colors.blue, fontSize: 16.0); - final String atText = toString(); +// final String atText = toString(); - return showAtBackground - ? BackgroundTextSpan( - background: Paint()..color = Colors.blue.withOpacity(0.15), - text: atText, - actualText: atText, - start: start!, +// return showAtBackground +// ? BackgroundTextSpan( +// background: Paint()..color = Colors.blue.withOpacity(0.15), +// text: atText, +// actualText: atText, +// start: start!, - ///caret can move into special text - deleteAll: true, - style: textStyle, - recognizer: (TapGestureRecognizer() - ..onTap = () { - if (onTap != null) { - onTap!(atText); - } - })) - : SpecialTextSpan( - text: atText, - actualText: atText, - start: start!, - style: textStyle, - recognizer: (TapGestureRecognizer() - ..onTap = () { - if (onTap != null) { - onTap!(atText); - } - })); - } -} +// ///caret can move into special text +// deleteAll: true, +// style: textStyle, +// recognizer: (TapGestureRecognizer() +// ..onTap = () { +// if (onTap != null) { +// onTap!(atText); +// } +// })) +// : SpecialTextSpan( +// text: atText, +// actualText: atText, +// start: start!, +// style: textStyle, +// recognizer: (TapGestureRecognizer() +// ..onTap = () { +// if (onTap != null) { +// onTap!(atText); +// } +// })); +// } +// } -List atList = [ - '@Nevermore ', - '@Dota2 ', - '@Biglao ', - '@艾莉亚·史塔克 ', - '@丹妮莉丝 ', - '@HandPulledNoodles ', - '@Zmtzawqlp ', - '@FaDeKongJian ', - '@CaiJingLongDaLao ', -]; +// List atList = [ +// '@Nevermore ', +// '@Dota2 ', +// '@Biglao ', +// '@艾莉亚·史塔克 ', +// '@丹妮莉丝 ', +// '@HandPulledNoodles ', +// '@Zmtzawqlp ', +// '@FaDeKongJian ', +// '@CaiJingLongDaLao ', +// ]; diff --git a/example/lib/common/text/dollar_text.dart b/example/lib/common/text/dollar_text.dart index 989feaee..362e898b 100644 --- a/example/lib/common/text/dollar_text.dart +++ b/example/lib/common/text/dollar_text.dart @@ -1,43 +1,43 @@ -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/gestures.dart'; -import 'package:flutter/material.dart'; +// import 'package:extended_text_library/extended_text_library.dart'; +// import 'package:flutter/gestures.dart'; +// import 'package:flutter/material.dart'; -class DollarText extends SpecialText { - DollarText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, - {this.start}) - : super(flag, flag, textStyle, onTap: onTap); - static const String flag = '\$'; - final int? start; - @override - InlineSpan finishText() { - final String text = getContent(); +// class DollarText extends SpecialText { +// DollarText(TextStyle? textStyle, SpecialTextGestureTapCallback? onTap, +// {this.start}) +// : super(flag, flag, textStyle, onTap: onTap); +// static const String flag = '\$'; +// final int? start; +// @override +// InlineSpan finishText() { +// final String text = getContent(); - return SpecialTextSpan( - text: text, - actualText: toString(), - start: start!, +// return SpecialTextSpan( +// text: text, +// actualText: toString(), +// start: start!, - ///caret can move into special text - deleteAll: true, - style: textStyle?.copyWith(color: Colors.orange), - recognizer: TapGestureRecognizer() - ..onTap = () { - if (onTap != null) { - onTap!(toString()); - } - }); - } -} +// ///caret can move into special text +// deleteAll: true, +// style: textStyle?.copyWith(color: Colors.orange), +// recognizer: TapGestureRecognizer() +// ..onTap = () { +// if (onTap != null) { +// onTap!(toString()); +// } +// }); +// } +// } -List dollarList = [ - '\$Dota2\$', - '\$Dota2 Ti9\$', - '\$CN dota best dota\$', - '\$Flutter\$', - '\$CN dev best dev\$', - '\$UWP\$', - '\$Nevermore\$', - '\$FlutterCandies\$', - '\$ExtendedImage\$', - '\$ExtendedText\$', -]; +// List dollarList = [ +// '\$Dota2\$', +// '\$Dota2 Ti9\$', +// '\$CN dota best dota\$', +// '\$Flutter\$', +// '\$CN dev best dev\$', +// '\$UWP\$', +// '\$Nevermore\$', +// '\$FlutterCandies\$', +// '\$ExtendedImage\$', +// '\$ExtendedText\$', +// ]; diff --git a/example/lib/common/text/emoji_text.dart b/example/lib/common/text/emoji_text.dart index eb79a939..705a78e1 100644 --- a/example/lib/common/text/emoji_text.dart +++ b/example/lib/common/text/emoji_text.dart @@ -1,53 +1,53 @@ -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/material.dart'; - -///emoji/image text -class EmojiText extends SpecialText { - EmojiText(TextStyle textStyle, {this.start}) - : super(EmojiText.flag, ']', textStyle); - static const String flag = '['; - final int? start; - @override - InlineSpan finishText() { - final String key = toString(); - - ///https://github.com/flutter/flutter/issues/42086 - /// widget span is not working on web - if (EmojiUitl.instance.emojiMap.containsKey(key)) { - //fontsize id define image height - //size = 30.0/26.0 * fontSize - const double size = 20.0; - - ///fontSize 26 and text height =30.0 - //final double fontSize = 26.0; - return ImageSpan( - AssetImage( - EmojiUitl.instance.emojiMap[key]!, - ), - actualText: key, - imageWidth: size, - imageHeight: size, - start: start!, - fit: BoxFit.fill, - margin: const EdgeInsets.only(left: 2.0, top: 2.0, right: 2.0)); - } - - return TextSpan(text: toString(), style: textStyle); - } -} - -class EmojiUitl { - EmojiUitl._() { - _emojiMap['[love]'] = '$_emojiFilePath/love.png'; - _emojiMap['[sun_glasses]'] = '$_emojiFilePath/sun_glasses.png'; - } - - final Map _emojiMap = {}; - - Map get emojiMap => _emojiMap; - - final String _emojiFilePath = 'assets'; - - static EmojiUitl? _instance; - static EmojiUitl get instance => _instance ??= EmojiUitl._(); -} +// import 'package:extended_text_library/extended_text_library.dart'; +// import 'package:flutter/material.dart'; + +// ///emoji/image text +// class EmojiText extends SpecialText { +// EmojiText(TextStyle textStyle, {this.start}) +// : super(EmojiText.flag, ']', textStyle); +// static const String flag = '['; +// final int? start; +// @override +// InlineSpan finishText() { +// final String key = toString(); + +// ///https://github.com/flutter/flutter/issues/42086 +// /// widget span is not working on web +// if (EmojiUitl.instance.emojiMap.containsKey(key)) { +// //fontsize id define image height +// //size = 30.0/26.0 * fontSize +// const double size = 20.0; + +// ///fontSize 26 and text height =30.0 +// //final double fontSize = 26.0; +// return ImageSpan( +// AssetImage( +// EmojiUitl.instance.emojiMap[key]!, +// ), +// actualText: key, +// imageWidth: size, +// imageHeight: size, +// start: start!, +// fit: BoxFit.fill, +// margin: const EdgeInsets.only(left: 2.0, top: 2.0, right: 2.0)); +// } + +// return TextSpan(text: toString(), style: textStyle); +// } +// } + +// class EmojiUitl { +// EmojiUitl._() { +// _emojiMap['[love]'] = '$_emojiFilePath/love.png'; +// _emojiMap['[sun_glasses]'] = '$_emojiFilePath/sun_glasses.png'; +// } + +// final Map _emojiMap = {}; + +// Map get emojiMap => _emojiMap; + +// final String _emojiFilePath = 'assets'; + +// static EmojiUitl? _instance; +// static EmojiUitl get instance => _instance ??= EmojiUitl._(); +// } diff --git a/example/lib/common/text/my_extended_text_selection_controls.dart b/example/lib/common/text/my_extended_text_selection_controls.dart index 95c91691..72c7bc85 100644 --- a/example/lib/common/text/my_extended_text_selection_controls.dart +++ b/example/lib/common/text/my_extended_text_selection_controls.dart @@ -1,313 +1,313 @@ -import 'dart:math' as math; -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:url_launcher/url_launcher.dart'; +// import 'dart:math' as math; +// import 'package:extended_text_library/extended_text_library.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter/services.dart'; +// import 'package:url_launcher/url_launcher.dart'; -/// -/// create by zmtzawqlp on 2019/8/3 -/// +// /// +// /// create by zmtzawqlp on 2019/8/3 +// /// -const double _kHandleSize = 22.0; +// const double _kHandleSize = 22.0; -// Padding between the toolbar and the anchor. -const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0; -const double _kToolbarContentDistance = 8.0; +// // Padding between the toolbar and the anchor. +// const double _kToolbarContentDistanceBelow = _kHandleSize - 2.0; +// const double _kToolbarContentDistance = 8.0; -/// Android Material styled text selection controls. -class MyTextSelectionControls extends TextSelectionControls { - MyTextSelectionControls({this.joinZeroWidthSpace = false}); - final bool joinZeroWidthSpace; +// /// Android Material styled text selection controls. +// class MyTextSelectionControls extends TextSelectionControls { +// MyTextSelectionControls({this.joinZeroWidthSpace = false}); +// final bool joinZeroWidthSpace; - @override - void handleCopy(TextSelectionDelegate delegate, - [ClipboardStatusNotifier? clipboardStatus]) { - final TextEditingValue value = delegate.textEditingValue; +// @override +// void handleCopy(TextSelectionDelegate delegate, +// [ClipboardStatusNotifier? clipboardStatus]) { +// final TextEditingValue value = delegate.textEditingValue; - String data = value.selection.textInside(value.text); - // remove zeroWidthSpace - if (joinZeroWidthSpace) { - data = data.replaceAll(zeroWidthSpace, ''); - } - Clipboard.setData(ClipboardData( - text: value.selection.textInside(value.text), - )); - clipboardStatus?.update(); - delegate.userUpdateTextEditingValue( - TextEditingValue( - text: value.text, - selection: TextSelection.collapsed(offset: value.selection.end), - ), - SelectionChangedCause.toolbar, - ); - delegate.bringIntoView(delegate.textEditingValue.selection.extent); - delegate.hideToolbar(); - } +// String data = value.selection.textInside(value.text); +// // remove zeroWidthSpace +// if (joinZeroWidthSpace) { +// data = data.replaceAll(zeroWidthSpace, ''); +// } +// Clipboard.setData(ClipboardData( +// text: value.selection.textInside(value.text), +// )); +// clipboardStatus?.update(); +// delegate.userUpdateTextEditingValue( +// TextEditingValue( +// text: value.text, +// selection: TextSelection.collapsed(offset: value.selection.end), +// ), +// SelectionChangedCause.toolbar, +// ); +// delegate.bringIntoView(delegate.textEditingValue.selection.extent); +// delegate.hideToolbar(); +// } - /// Returns the size of the Material handle. - @override - Size getHandleSize(double textLineHeight) => - const Size(_kHandleSize, _kHandleSize); +// /// Returns the size of the Material handle. +// @override +// Size getHandleSize(double textLineHeight) => +// const Size(_kHandleSize, _kHandleSize); - /// Builder for material-style copy/paste text selection toolbar. - @override - Widget buildToolbar( - BuildContext context, - Rect globalEditableRegion, - double textLineHeight, - Offset selectionMidpoint, - List endpoints, - TextSelectionDelegate delegate, - ClipboardStatusNotifier? clipboardStatus, - Offset? lastSecondaryTapDownPosition, - ) { - return _TextSelectionControlsToolbar( - globalEditableRegion: globalEditableRegion, - textLineHeight: textLineHeight, - selectionMidpoint: selectionMidpoint, - endpoints: endpoints, - delegate: delegate, - clipboardStatus: clipboardStatus, - handleCut: canCut(delegate) ? () => handleCut(delegate, null) : null, - handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null, - handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, - handleSelectAll: - canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, - handleLike: () { - launchUrl(Uri.parse( - 'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${delegate.textEditingValue.text}')); - delegate.hideToolbar(); - delegate.userUpdateTextEditingValue( - delegate.textEditingValue - .copyWith(selection: const TextSelection.collapsed(offset: 0)), - SelectionChangedCause.toolbar, - ); - }, - ); - } +// /// Builder for material-style copy/paste text selection toolbar. +// @override +// Widget buildToolbar( +// BuildContext context, +// Rect globalEditableRegion, +// double textLineHeight, +// Offset selectionMidpoint, +// List endpoints, +// TextSelectionDelegate delegate, +// ClipboardStatusNotifier? clipboardStatus, +// Offset? lastSecondaryTapDownPosition, +// ) { +// return _TextSelectionControlsToolbar( +// globalEditableRegion: globalEditableRegion, +// textLineHeight: textLineHeight, +// selectionMidpoint: selectionMidpoint, +// endpoints: endpoints, +// delegate: delegate, +// clipboardStatus: clipboardStatus, +// handleCut: canCut(delegate) ? () => handleCut(delegate, null) : null, +// handleCopy: canCopy(delegate) ? () => handleCopy(delegate) : null, +// handlePaste: canPaste(delegate) ? () => handlePaste(delegate) : null, +// handleSelectAll: +// canSelectAll(delegate) ? () => handleSelectAll(delegate) : null, +// handleLike: () { +// launchUrl(Uri.parse( +// 'mailto:zmtzawqlp@live.com?subject=extended_text_share&body=${delegate.textEditingValue.text}')); +// delegate.hideToolbar(); +// delegate.userUpdateTextEditingValue( +// delegate.textEditingValue +// .copyWith(selection: const TextSelection.collapsed(offset: 0)), +// SelectionChangedCause.toolbar, +// ); +// }, +// ); +// } - /// Builder for material-style text selection handles. - @override - Widget buildHandle( - BuildContext context, TextSelectionHandleType type, double textLineHeight, - [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { - final Widget handle = SizedBox( - width: _kHandleSize, - height: _kHandleSize, - child: Image.asset( - 'assets/40.png', - ), - ); +// /// Builder for material-style text selection handles. +// @override +// Widget buildHandle( +// BuildContext context, TextSelectionHandleType type, double textLineHeight, +// [VoidCallback? onTap, double? startGlyphHeight, double? endGlyphHeight]) { +// final Widget handle = SizedBox( +// width: _kHandleSize, +// height: _kHandleSize, +// child: Image.asset( +// 'assets/40.png', +// ), +// ); - // [handle] is a circle, with a rectangle in the top left quadrant of that - // circle (an onion pointing to 10:30). We rotate [handle] to point - // straight up or up-right depending on the handle type. - switch (type) { - case TextSelectionHandleType.left: // points up-right - return Transform.rotate( - angle: math.pi / 4.0, - child: handle, - ); - case TextSelectionHandleType.right: // points up-left - return Transform.rotate( - angle: -math.pi / 4.0, - child: handle, - ); - case TextSelectionHandleType.collapsed: // points up - return handle; - } - } +// // [handle] is a circle, with a rectangle in the top left quadrant of that +// // circle (an onion pointing to 10:30). We rotate [handle] to point +// // straight up or up-right depending on the handle type. +// switch (type) { +// case TextSelectionHandleType.left: // points up-right +// return Transform.rotate( +// angle: math.pi / 4.0, +// child: handle, +// ); +// case TextSelectionHandleType.right: // points up-left +// return Transform.rotate( +// angle: -math.pi / 4.0, +// child: handle, +// ); +// case TextSelectionHandleType.collapsed: // points up +// return handle; +// } +// } - /// Gets anchor for material-style text selection handles. - /// - /// See [TextSelectionControls.getHandleAnchor]. - @override - Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, - [double? startGlyphHeight, double? endGlyphHeight]) { - switch (type) { - case TextSelectionHandleType.left: - return const Offset(_kHandleSize, 0); - case TextSelectionHandleType.right: - return Offset.zero; - default: - return const Offset(_kHandleSize / 2, -4); - } - } +// /// Gets anchor for material-style text selection handles. +// /// +// /// See [TextSelectionControls.getHandleAnchor]. +// @override +// Offset getHandleAnchor(TextSelectionHandleType type, double textLineHeight, +// [double? startGlyphHeight, double? endGlyphHeight]) { +// switch (type) { +// case TextSelectionHandleType.left: +// return const Offset(_kHandleSize, 0); +// case TextSelectionHandleType.right: +// return Offset.zero; +// default: +// return const Offset(_kHandleSize / 2, -4); +// } +// } - @override - bool canSelectAll(TextSelectionDelegate delegate) { - // Android allows SelectAll when selection is not collapsed, unless - // everything has already been selected. - final TextEditingValue value = delegate.textEditingValue; - return delegate.selectAllEnabled && - value.text.isNotEmpty && - !(value.selection.start == 0 && - value.selection.end == value.text.length); - } -} +// @override +// bool canSelectAll(TextSelectionDelegate delegate) { +// // Android allows SelectAll when selection is not collapsed, unless +// // everything has already been selected. +// final TextEditingValue value = delegate.textEditingValue; +// return delegate.selectAllEnabled && +// value.text.isNotEmpty && +// !(value.selection.start == 0 && +// value.selection.end == value.text.length); +// } +// } -// The label and callback for the available default text selection menu buttons. -class _TextSelectionToolbarItemData { - const _TextSelectionToolbarItemData({ - required this.label, - required this.onPressed, - }); +// // The label and callback for the available default text selection menu buttons. +// class _TextSelectionToolbarItemData { +// const _TextSelectionToolbarItemData({ +// required this.label, +// required this.onPressed, +// }); - final String label; - final VoidCallback? onPressed; -} +// final String label; +// final VoidCallback? onPressed; +// } -// The highest level toolbar widget, built directly by buildToolbar. -class _TextSelectionControlsToolbar extends StatefulWidget { - const _TextSelectionControlsToolbar({ - required this.clipboardStatus, - required this.delegate, - required this.endpoints, - required this.globalEditableRegion, - required this.handleCut, - required this.handleCopy, - required this.handlePaste, - required this.handleSelectAll, - required this.selectionMidpoint, - required this.textLineHeight, - required this.handleLike, - }); +// // The highest level toolbar widget, built directly by buildToolbar. +// class _TextSelectionControlsToolbar extends StatefulWidget { +// const _TextSelectionControlsToolbar({ +// required this.clipboardStatus, +// required this.delegate, +// required this.endpoints, +// required this.globalEditableRegion, +// required this.handleCut, +// required this.handleCopy, +// required this.handlePaste, +// required this.handleSelectAll, +// required this.selectionMidpoint, +// required this.textLineHeight, +// required this.handleLike, +// }); - final ClipboardStatusNotifier? clipboardStatus; - final TextSelectionDelegate delegate; - final List endpoints; - final Rect globalEditableRegion; - final VoidCallback? handleCut; - final VoidCallback? handleCopy; - final VoidCallback? handlePaste; - final VoidCallback? handleSelectAll; - final VoidCallback? handleLike; - final Offset selectionMidpoint; - final double textLineHeight; +// final ClipboardStatusNotifier? clipboardStatus; +// final TextSelectionDelegate delegate; +// final List endpoints; +// final Rect globalEditableRegion; +// final VoidCallback? handleCut; +// final VoidCallback? handleCopy; +// final VoidCallback? handlePaste; +// final VoidCallback? handleSelectAll; +// final VoidCallback? handleLike; +// final Offset selectionMidpoint; +// final double textLineHeight; - @override - _TextSelectionControlsToolbarState createState() => - _TextSelectionControlsToolbarState(); -} +// @override +// _TextSelectionControlsToolbarState createState() => +// _TextSelectionControlsToolbarState(); +// } -class _TextSelectionControlsToolbarState - extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin { - void _onChangedClipboardStatus() { - setState(() { - // Inform the widget that the value of clipboardStatus has changed. - }); - } +// class _TextSelectionControlsToolbarState +// extends State<_TextSelectionControlsToolbar> with TickerProviderStateMixin { +// void _onChangedClipboardStatus() { +// setState(() { +// // Inform the widget that the value of clipboardStatus has changed. +// }); +// } - @override - void initState() { - super.initState(); - widget.clipboardStatus?.addListener(_onChangedClipboardStatus); - } +// @override +// void initState() { +// super.initState(); +// widget.clipboardStatus?.addListener(_onChangedClipboardStatus); +// } - @override - void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) { - super.didUpdateWidget(oldWidget); - if (widget.clipboardStatus != oldWidget.clipboardStatus) { - widget.clipboardStatus?.addListener(_onChangedClipboardStatus); - oldWidget.clipboardStatus?.removeListener(_onChangedClipboardStatus); - } - } +// @override +// void didUpdateWidget(_TextSelectionControlsToolbar oldWidget) { +// super.didUpdateWidget(oldWidget); +// if (widget.clipboardStatus != oldWidget.clipboardStatus) { +// widget.clipboardStatus?.addListener(_onChangedClipboardStatus); +// oldWidget.clipboardStatus?.removeListener(_onChangedClipboardStatus); +// } +// } - @override - void dispose() { - super.dispose(); - widget.clipboardStatus?.removeListener(_onChangedClipboardStatus); - } +// @override +// void dispose() { +// super.dispose(); +// widget.clipboardStatus?.removeListener(_onChangedClipboardStatus); +// } - @override - Widget build(BuildContext context) { - // If there are no buttons to be shown, don't render anything. - if (widget.handleCut == null && - widget.handleCopy == null && - widget.handlePaste == null && - widget.handleSelectAll == null) { - return const SizedBox.shrink(); - } - // If the paste button is desired, don't render anything until the state of - // the clipboard is known, since it's used to determine if paste is shown. - if (widget.handlePaste != null && - widget.clipboardStatus?.value == ClipboardStatus.unknown) { - return const SizedBox.shrink(); - } +// @override +// Widget build(BuildContext context) { +// // If there are no buttons to be shown, don't render anything. +// if (widget.handleCut == null && +// widget.handleCopy == null && +// widget.handlePaste == null && +// widget.handleSelectAll == null) { +// return const SizedBox.shrink(); +// } +// // If the paste button is desired, don't render anything until the state of +// // the clipboard is known, since it's used to determine if paste is shown. +// if (widget.handlePaste != null && +// widget.clipboardStatus?.value == ClipboardStatus.unknown) { +// return const SizedBox.shrink(); +// } - // Calculate the positioning of the menu. It is placed above the selection - // if there is enough room, or otherwise below. - final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0]; - final TextSelectionPoint endTextSelectionPoint = - widget.endpoints.length > 1 ? widget.endpoints[1] : widget.endpoints[0]; - final Offset anchorAbove = Offset( - widget.globalEditableRegion.left + widget.selectionMidpoint.dx, - widget.globalEditableRegion.top + - startTextSelectionPoint.point.dy - - widget.textLineHeight - - _kToolbarContentDistance, - ); - final Offset anchorBelow = Offset( - widget.globalEditableRegion.left + widget.selectionMidpoint.dx, - widget.globalEditableRegion.top + - endTextSelectionPoint.point.dy + - _kToolbarContentDistanceBelow, - ); +// // Calculate the positioning of the menu. It is placed above the selection +// // if there is enough room, or otherwise below. +// final TextSelectionPoint startTextSelectionPoint = widget.endpoints[0]; +// final TextSelectionPoint endTextSelectionPoint = +// widget.endpoints.length > 1 ? widget.endpoints[1] : widget.endpoints[0]; +// final Offset anchorAbove = Offset( +// widget.globalEditableRegion.left + widget.selectionMidpoint.dx, +// widget.globalEditableRegion.top + +// startTextSelectionPoint.point.dy - +// widget.textLineHeight - +// _kToolbarContentDistance, +// ); +// final Offset anchorBelow = Offset( +// widget.globalEditableRegion.left + widget.selectionMidpoint.dx, +// widget.globalEditableRegion.top + +// endTextSelectionPoint.point.dy + +// _kToolbarContentDistanceBelow, +// ); - // Determine which buttons will appear so that the order and total number is - // known. A button's position in the menu can slightly affect its - // appearance. - assert(debugCheckHasMaterialLocalizations(context)); - final MaterialLocalizations localizations = - MaterialLocalizations.of(context); - final List<_TextSelectionToolbarItemData> itemDatas = - <_TextSelectionToolbarItemData>[ - if (widget.handleCut != null) - _TextSelectionToolbarItemData( - label: localizations.cutButtonLabel, - onPressed: widget.handleCut!, - ), - if (widget.handleCopy != null) - _TextSelectionToolbarItemData( - label: localizations.copyButtonLabel, - onPressed: widget.handleCopy!, - ), - if (widget.handlePaste != null && - widget.clipboardStatus?.value == ClipboardStatus.pasteable) - _TextSelectionToolbarItemData( - label: localizations.pasteButtonLabel, - onPressed: widget.handlePaste!, - ), - if (widget.handleSelectAll != null) - _TextSelectionToolbarItemData( - label: localizations.selectAllButtonLabel, - onPressed: widget.handleSelectAll!, - ), - _TextSelectionToolbarItemData( - label: 'like', - onPressed: widget.handleLike, - ), - ]; +// // Determine which buttons will appear so that the order and total number is +// // known. A button's position in the menu can slightly affect its +// // appearance. +// assert(debugCheckHasMaterialLocalizations(context)); +// final MaterialLocalizations localizations = +// MaterialLocalizations.of(context); +// final List<_TextSelectionToolbarItemData> itemDatas = +// <_TextSelectionToolbarItemData>[ +// if (widget.handleCut != null) +// _TextSelectionToolbarItemData( +// label: localizations.cutButtonLabel, +// onPressed: widget.handleCut!, +// ), +// if (widget.handleCopy != null) +// _TextSelectionToolbarItemData( +// label: localizations.copyButtonLabel, +// onPressed: widget.handleCopy!, +// ), +// if (widget.handlePaste != null && +// widget.clipboardStatus?.value == ClipboardStatus.pasteable) +// _TextSelectionToolbarItemData( +// label: localizations.pasteButtonLabel, +// onPressed: widget.handlePaste!, +// ), +// if (widget.handleSelectAll != null) +// _TextSelectionToolbarItemData( +// label: localizations.selectAllButtonLabel, +// onPressed: widget.handleSelectAll!, +// ), +// _TextSelectionToolbarItemData( +// label: 'like', +// onPressed: widget.handleLike, +// ), +// ]; - // If there is no option available, build an empty widget. - if (itemDatas.isEmpty) { - return const SizedBox(width: 0.0, height: 0.0); - } +// // If there is no option available, build an empty widget. +// if (itemDatas.isEmpty) { +// return const SizedBox(width: 0.0, height: 0.0); +// } - return TextSelectionToolbar( - anchorAbove: anchorAbove, - anchorBelow: anchorBelow, - children: itemDatas - .asMap() - .entries - .map((MapEntry entry) { - return TextSelectionToolbarTextButton( - padding: TextSelectionToolbarTextButton.getPadding( - entry.key, itemDatas.length), - onPressed: entry.value.onPressed, - child: Text(entry.value.label), - ); - }).toList(), - ); - } -} +// return TextSelectionToolbar( +// anchorAbove: anchorAbove, +// anchorBelow: anchorBelow, +// children: itemDatas +// .asMap() +// .entries +// .map((MapEntry entry) { +// return TextSelectionToolbarTextButton( +// padding: TextSelectionToolbarTextButton.getPadding( +// entry.key, itemDatas.length), +// onPressed: entry.value.onPressed, +// child: Text(entry.value.label), +// ); +// }).toList(), +// ); +// } +// } diff --git a/example/lib/common/text/my_special_text_span_builder.dart b/example/lib/common/text/my_special_text_span_builder.dart index db2c8705..c7e93eae 100644 --- a/example/lib/common/text/my_special_text_span_builder.dart +++ b/example/lib/common/text/my_special_text_span_builder.dart @@ -1,39 +1,39 @@ -import 'package:extended_text_library/extended_text_library.dart'; -import 'package:flutter/material.dart'; +// import 'package:extended_text_library/extended_text_library.dart'; +// import 'package:flutter/material.dart'; -import 'at_text.dart'; -import 'dollar_text.dart'; -import 'emoji_text.dart'; +// import 'at_text.dart'; +// import 'dollar_text.dart'; +// import 'emoji_text.dart'; -class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { - MySpecialTextSpanBuilder({this.showAtBackground = false}); +// class MySpecialTextSpanBuilder extends SpecialTextSpanBuilder { +// MySpecialTextSpanBuilder({this.showAtBackground = false}); - /// whether show background for @somebody - final bool showAtBackground; +// /// whether show background for @somebody +// final bool showAtBackground; - @override - SpecialText? createSpecialText(String flag, - {TextStyle? textStyle, - SpecialTextGestureTapCallback? onTap, - int? index}) { - if (flag == '') { - return null; - } +// @override +// SpecialText? createSpecialText(String flag, +// {TextStyle? textStyle, +// SpecialTextGestureTapCallback? onTap, +// int? index}) { +// if (flag == '') { +// return null; +// } - ///index is end index of start flag, so text start index should be index-(flag.length-1) - if (isStart(flag, AtText.flag)) { - return AtText( - textStyle!, - onTap, - start: index! - (AtText.flag.length - 1), - showAtBackground: showAtBackground, - ); - } else if (isStart(flag, EmojiText.flag)) { - return EmojiText(textStyle!, start: index! - (EmojiText.flag.length - 1)); - } else if (isStart(flag, DollarText.flag)) { - return DollarText(textStyle!, onTap, - start: index! - (DollarText.flag.length - 1)); - } - return null; - } -} +// ///index is end index of start flag, so text start index should be index-(flag.length-1) +// if (isStart(flag, AtText.flag)) { +// return AtText( +// textStyle!, +// onTap, +// start: index! - (AtText.flag.length - 1), +// showAtBackground: showAtBackground, +// ); +// } else if (isStart(flag, EmojiText.flag)) { +// return EmojiText(textStyle!, start: index! - (EmojiText.flag.length - 1)); +// } else if (isStart(flag, DollarText.flag)) { +// return DollarText(textStyle!, onTap, +// start: index! - (DollarText.flag.length - 1)); +// } +// return null; +// } +// } diff --git a/example/lib/common/widget/memory_usage_chart.dart b/example/lib/common/widget/memory_usage_chart.dart index add727e6..4ae2bb28 100644 --- a/example/lib/common/widget/memory_usage_chart.dart +++ b/example/lib/common/widget/memory_usage_chart.dart @@ -1,5 +1,4 @@ import 'dart:math'; -import 'dart:ui'; import 'package:example/common/utils/vm_helper.dart'; import 'package:fl_chart/fl_chart.dart'; @@ -37,7 +36,7 @@ class _MemoryUsageChartState extends State { } return Container( padding: const EdgeInsets.only(left: 30, right: 30, top: 20, bottom: 5), - width: window.physicalSize.width, + width: View.of(context).physicalSize.width, height: 150, child: LineChart( getData(), diff --git a/example/lib/common/widget/memory_usage_view.dart b/example/lib/common/widget/memory_usage_view.dart index bfb916be..00a8f33d 100644 --- a/example/lib/common/widget/memory_usage_view.dart +++ b/example/lib/common/widget/memory_usage_view.dart @@ -16,9 +16,14 @@ class _MemoryUsageViewState extends State { void initState() { super.initState(); VMHelper().addListener(_updateMemoryUsage); + } - _top = window.physicalSize.height / window.devicePixelRatio / 2 - 80; - _left = window.physicalSize.width / window.devicePixelRatio / 2 - 40; + @override + void didChangeDependencies() { + final FlutterView view = View.of(context); + _top = view.physicalSize.height / view.devicePixelRatio / 2 - 80; + _left = view.physicalSize.width / view.devicePixelRatio / 2 - 40; + super.didChangeDependencies(); } void _updateMemoryUsage() { diff --git a/example/lib/common/widget/pic_swiper.dart b/example/lib/common/widget/pic_swiper.dart index 13c6f7e8..d2a5e764 100644 --- a/example/lib/common/widget/pic_swiper.dart +++ b/example/lib/common/widget/pic_swiper.dart @@ -5,16 +5,16 @@ import 'dart:math'; import 'package:example/common/data/tu_chong_source.dart' hide asT; @FFArgumentImport() import 'package:example/common/model/pic_swiper_item.dart'; -import 'package:example/common/text/my_extended_text_selection_controls.dart'; -import 'package:example/common/text/my_special_text_span_builder.dart'; +// import 'package:example/common/text/my_extended_text_selection_controls.dart'; +// import 'package:example/common/text/my_special_text_span_builder.dart'; import 'package:example/common/utils/util.dart'; import 'package:extended_image/extended_image.dart'; -import 'package:extended_text/extended_text.dart'; +//import 'package:extended_text/extended_text.dart'; import 'package:ff_annotation_route_library/ff_annotation_route_library.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide Image; import 'package:oktoast/oktoast.dart'; -import 'package:url_launcher/url_launcher.dart'; +// import 'package:url_launcher/url_launcher.dart'; import 'hero.dart'; import 'item_builder.dart'; @@ -83,43 +83,43 @@ class ImageDetail extends StatelessWidget { const SizedBox( height: 15.0, ), - ExtendedText( + Text( content, - onSpecialTextTap: (dynamic parameter) { - if (parameter.toString().startsWith('\$')) { - launchUrl(Uri.parse('https://github.com/fluttercandies')); - } else if (parameter.toString().startsWith('@')) { - launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); - } - }, - specialTextSpanBuilder: MySpecialTextSpanBuilder(), + // onSpecialTextTap: (dynamic parameter) { + // if (parameter.toString().startsWith('\$')) { + // launchUrl(Uri.parse('https://github.com/fluttercandies')); + // } else if (parameter.toString().startsWith('@')) { + // launchUrl(Uri.parse('mailto:zmtzawqlp@live.com')); + // } + // }, + // specialTextSpanBuilder: MySpecialTextSpanBuilder(), //overflow: ExtendedTextOverflow.ellipsis, style: const TextStyle(fontSize: 14, color: Colors.grey), maxLines: 10, - overflowWidget: TextOverflowWidget( - //maxHeight: double.infinity, - //align: TextOverflowAlign.right, - //fixedOffset: Offset.zero, - //debugOverflowRectColor: Colors.red, - child: DefaultTextStyle( - style: const TextStyle(fontSize: 12, color: Colors.blue), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('\u2026 '), - GestureDetector( - child: const Text('more'), - onTap: () { - launchUrl(Uri.parse( - 'https://github.com/fluttercandies/extended_text')); - }, - ) - ], - ), - ), - ), - selectionEnabled: true, - selectionControls: MyTextSelectionControls(), + // overflowWidget: TextOverflowWidget( + // //maxHeight: double.infinity, + // //align: TextOverflowAlign.right, + // //fixedOffset: Offset.zero, + // //debugOverflowRectColor: Colors.red, + // child: DefaultTextStyle( + // style: const TextStyle(fontSize: 12, color: Colors.blue), + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const Text('\u2026 '), + // GestureDetector( + // child: const Text('more'), + // onTap: () { + // launchUrl(Uri.parse( + // 'https://github.com/fluttercandies/extended_text')); + // }, + // ) + // ], + // ), + // ), + // ), + // selectionEnabled: true, + // selectionControls: MyTextSelectionControls(), ), const SizedBox( height: 20.0, @@ -149,7 +149,7 @@ class ImageDetail extends StatelessWidget { '${tuChongItem?.imageSize.width.toInt()} * ${tuChongItem?.imageSize.height.toInt()}', ), ), - Positioned( + const Positioned( top: -33.0, right: 0, left: 0, @@ -157,7 +157,7 @@ class ImageDetail extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, - children: const [ + children: [ Icon( Icons.star, color: Colors.yellow, @@ -197,37 +197,37 @@ class ImageDetail extends StatelessWidget { BoxShadow(color: Colors.grey, blurRadius: 15.0, spreadRadius: 20.0), ]), ); - - return ExtendedTextSelectionPointerHandler( - //default behavior - // child: result, - //custom your behavior - builder: (List states) { - return GestureDetector( - onTap: () { - //do not pop page - }, - child: Listener( - child: result, - behavior: HitTestBehavior.translucent, - onPointerDown: (PointerDownEvent value) { - for (final ExtendedTextSelectionState state in states) { - if (!state.containsPosition(value.position)) { - //clear other selection - state.clearSelection(); - } - } - }, - onPointerMove: (PointerMoveEvent value) { - //clear other selection - for (final ExtendedTextSelectionState state in states) { - state.clearSelection(); - } - }, - ), - ); - }, - ); + return result; + // return ExtendedTextSelectionPointerHandler( + // //default behavior + // // child: result, + // //custom your behavior + // builder: (List states) { + // return GestureDetector( + // onTap: () { + // //do not pop page + // }, + // child: Listener( + // child: result, + // behavior: HitTestBehavior.translucent, + // onPointerDown: (PointerDownEvent value) { + // for (final ExtendedTextSelectionState state in states) { + // if (!state.containsPosition(value.position)) { + // //clear other selection + // state.clearSelection(); + // } + // } + // }, + // onPointerMove: (PointerMoveEvent value) { + // //clear other selection + // for (final ExtendedTextSelectionState state in states) { + // state.clearSelection(); + // } + // }, + // ), + // ); + // }, + // ); } } diff --git a/example/lib/main.dart b/example/lib/main.dart index f4a7041a..5264f63d 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -49,17 +49,6 @@ class MyApp extends StatelessWidget { return onGenerateRoute( settings: settings, getRouteSettings: getRouteSettings, - // routeSettingsWrapper: (FFRouteSettings ffRouteSettings) { - // if (ffRouteSettings.name == Routes.fluttercandiesMainpage || - // ffRouteSettings.name == Routes.fluttercandiesDemogrouppage) { - // return ffRouteSettings; - // } - // return ffRouteSettings.copyWith( - // widget: CommonWidget( - // child: ffRouteSettings.widget, - // title: ffRouteSettings.routeName, - // )); - // }, ); }, )); diff --git a/example/lib/pages/complex/image_editor_demo.dart b/example/lib/pages/complex/image_editor_demo.dart index 6564cfab..58521829 100644 --- a/example/lib/pages/complex/image_editor_demo.dart +++ b/example/lib/pages/complex/image_editor_demo.dart @@ -221,9 +221,9 @@ class _ImageEditorDemoState extends State { initialValue: _cropLayerPainter, itemBuilder: (BuildContext context) { return >[ - PopupMenuItem( + const PopupMenuItem( child: Row( - children: const [ + children: [ Icon( Icons.rounded_corner_sharp, color: Colors.blue, @@ -234,12 +234,12 @@ class _ImageEditorDemoState extends State { Text('Default'), ], ), - value: const EditorCropLayerPainter(), + value: EditorCropLayerPainter(), ), const PopupMenuDivider(), - PopupMenuItem( + const PopupMenuItem( child: Row( - children: const [ + children: [ Icon( Icons.circle, color: Colors.blue, @@ -250,12 +250,12 @@ class _ImageEditorDemoState extends State { Text('Custom'), ], ), - value: const CustomEditorCropLayerPainter(), + value: CustomEditorCropLayerPainter(), ), const PopupMenuDivider(), - PopupMenuItem( + const PopupMenuItem( child: Row( - children: const [ + children: [ Icon( CupertinoIcons.circle, color: Colors.blue, @@ -266,7 +266,7 @@ class _ImageEditorDemoState extends State { Text('Circle'), ], ), - value: const CircleEditorCropLayerPainter(), + value: CircleEditorCropLayerPainter(), ), ]; }, diff --git a/example/lib/pages/complex/photo_view_demo.dart b/example/lib/pages/complex/photo_view_demo.dart index 63456131..bd8e4b43 100644 --- a/example/lib/pages/complex/photo_view_demo.dart +++ b/example/lib/pages/complex/photo_view_demo.dart @@ -6,19 +6,16 @@ import 'dart:async'; import 'package:example/common/data/tu_chong_repository.dart'; import 'package:example/common/data/tu_chong_source.dart'; -import 'package:example/common/text/my_extended_text_selection_controls.dart'; -import 'package:example/common/text/my_special_text_span_builder.dart'; import 'package:example/common/utils/vm_helper.dart'; import 'package:example/common/widget/item_builder.dart'; import 'package:example/common/widget/pic_grid_view.dart'; import 'package:example/common/widget/push_to_refresh_header.dart'; import 'package:extended_image/extended_image.dart'; -import 'package:extended_text/extended_text.dart'; +// import 'package:extended_text/extended_text.dart'; import 'package:ff_annotation_route_core/ff_annotation_route_core.dart'; import 'package:flutter/material.dart' hide CircularProgressIndicator; import 'package:loading_more_list/loading_more_list.dart'; import 'package:pull_to_refresh_notification/pull_to_refresh_notification.dart'; -import 'package:url_launcher/url_launcher.dart'; @FFRoute( name: 'fluttercandies://photoview', @@ -35,7 +32,7 @@ class PhotoViewDemo extends StatefulWidget { } class _PhotoViewDemoState extends State { - MyTextSelectionControls? _myExtendedMaterialTextSelectionControls; + // MyTextSelectionControls? _myExtendedMaterialTextSelectionControls; final String _attachContent = '[love]Extended text help you to build rich text quickly. any special text you will have with extended text.It\'s my pleasure to invite you to join \$FlutterCandies\$ if you want to improve flutter .[love] if you meet any problem, please let me konw @zmtzawqlp .[sun_glasses]'; TuChongRepository listSourceRepository = TuChongRepository(); @@ -137,43 +134,43 @@ class _PhotoViewDemoState extends State { ), ), Padding( - child: ExtendedText( + child: Text( content, - onSpecialTextTap: (dynamic parameter) { - if (parameter.toString().startsWith('\$')) { - launchUrl(Uri.parse( - 'https://github.com/fluttercandies')); - } else if (parameter - .toString() - .startsWith('@')) { - launchUrl(Uri.parse( - 'mailto:zmtzawqlp@live.com')); - } - }, - specialTextSpanBuilder: - MySpecialTextSpanBuilder(), + // onSpecialTextTap: (dynamic parameter) { + // if (parameter.toString().startsWith('\$')) { + // launchUrl(Uri.parse( + // 'https://github.com/fluttercandies')); + // } else if (parameter + // .toString() + // .startsWith('@')) { + // launchUrl(Uri.parse( + // 'mailto:zmtzawqlp@live.com')); + // } + // }, + // specialTextSpanBuilder: + // MySpecialTextSpanBuilder(), //overflow: ExtendedTextOverflow.ellipsis, style: const TextStyle( fontSize: 14, color: Colors.grey), maxLines: 5, - overflowWidget: TextOverflowWidget( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Text('\u2026 '), - InkWell( - child: const Text('more'), - onTap: () { - launchUrl(Uri.parse( - 'https://github.com/fluttercandies/extended_text')); - }, - ) - ], - ), - ), - selectionEnabled: true, - selectionControls: - _myExtendedMaterialTextSelectionControls, + // overflowWidget: TextOverflowWidget( + // child: Row( + // mainAxisSize: MainAxisSize.min, + // children: [ + // const Text('\u2026 '), + // InkWell( + // child: const Text('more'), + // onTap: () { + // launchUrl(Uri.parse( + // 'https://github.com/fluttercandies/extended_text')); + // }, + // ) + // ], + // ), + // ), + // selectionEnabled: true, + // selectionControls: + // _myExtendedMaterialTextSelectionControls, ), padding: const EdgeInsets.only( left: margin, @@ -212,32 +209,32 @@ class _PhotoViewDemoState extends State { ], ), ); - - return ExtendedTextSelectionPointerHandler( - //default behavior - // child: result, - //custom your behavior - builder: (List states) { - return Listener( - child: result, - behavior: HitTestBehavior.translucent, - onPointerDown: (PointerDownEvent value) { - for (final ExtendedTextSelectionState state in states) { - if (!state.containsPosition(value.position)) { - //clear other selection - state.clearSelection(); - } - } - }, - onPointerMove: (PointerMoveEvent value) { - //clear other selection - for (final ExtendedTextSelectionState state in states) { - state.clearSelection(); - } - }, - ); - }, - ); + return result; + // return ExtendedTextSelectionPointerHandler( + // //default behavior + // // child: result, + // //custom your behavior + // builder: (List states) { + // return Listener( + // child: result, + // behavior: HitTestBehavior.translucent, + // onPointerDown: (PointerDownEvent value) { + // for (final ExtendedTextSelectionState state in states) { + // if (!state.containsPosition(value.position)) { + // //clear other selection + // state.clearSelection(); + // } + // } + // }, + // onPointerMove: (PointerMoveEvent value) { + // //clear other selection + // for (final ExtendedTextSelectionState state in states) { + // state.clearSelection(); + // } + // }, + // ); + // }, + // ); } @override @@ -251,7 +248,7 @@ class _PhotoViewDemoState extends State { @override void initState() { - _myExtendedMaterialTextSelectionControls = MyTextSelectionControls(); + // _myExtendedMaterialTextSelectionControls = MyTextSelectionControls(); super.initState(); } diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 5b758efd..f93eaee7 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -4,33 +4,34 @@ publish_to: 'none' version: 1.0.0+1 environment: - sdk: '>=2.18.0 <3.0.0' - flutter: '>=3.7.0' + sdk: '>=2.18.0 <4.0.0' + flutter: '>=3.10.0' dependencies: - extended_sliver: ^2.0.1 - extended_text: ^9.0.0 - ff_annotation_route_library: ^3.0.0 - fl_chart: ^0.55.1 + extended_sliver: any + ff_annotation_route_library: any + fl_chart: any flutter: sdk: flutter - http_client_helper: ^2.0.2 - image: ^3.1.3 - image_editor: ^1.0.2 - intl: ^0.17.0 - js: ^0.6.3 - like_button: ^2.0.4 - loading_more_list: ^5.0.2 - oktoast: ^3.1.4 - photo_manager: ^2.0.1 - pull_to_refresh_notification: ^2.3.0 - url_launcher: ^6.0.20 - vm_service: ^9.3.0 - wechat_assets_picker: ^8.0.2 + http_client_helper: any + image: any + image_editor: any + intl: any + js: any + like_button: any + loading_more_list: any + oktoast: any + photo_manager: any + pull_to_refresh_notification: any + url_launcher: any + vm_service: any + wechat_assets_picker: any dependency_overrides: extended_image: path: ../ + path_provider: ^2.0.15 + flutter: uses-material-design: true diff --git a/example/web/icons/Icon-maskable-192.png b/example/web/icons/Icon-maskable-192.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9b4d76e525556d5d89141648c724331630325d GIT binary patch literal 5594 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 X>c!)3PgKfL&lX^$Z>Cpu&6)6jvi^Z! literal 0 HcmV?d00001 diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 0000000000000000000000000000000000000000..d69c56691fbdb0b7efa65097c7cc1edac12a6d3e GIT binary patch literal 20998 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 MPgg&ebxsLQ025IeI{*Lx literal 0 HcmV?d00001 diff --git a/lib/src/extended_image.dart b/lib/src/extended_image.dart index 9a2333fb..4fd71d09 100644 --- a/lib/src/extended_image.dart +++ b/lib/src/extended_image.dart @@ -7,6 +7,7 @@ import 'package:extended_image_library/extended_image_library.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart' hide Image; +import 'package:flutter/scheduler.dart'; import 'package:flutter/semantics.dart'; import 'editor/editor.dart'; @@ -1239,7 +1240,9 @@ class _ExtendedImageState extends State } void _replaceImage({required ImageInfo? info}) { - _imageInfo?.dispose(); + final ImageInfo? oldImageInfo = _imageInfo; + SchedulerBinding.instance + .addPostFrameCallback((_) => oldImageInfo?.dispose()); _imageInfo = info; } diff --git a/lib/src/gesture/gesture.dart b/lib/src/gesture/gesture.dart index 58e20a2d..102d9622 100644 --- a/lib/src/gesture/gesture.dart +++ b/lib/src/gesture/gesture.dart @@ -249,13 +249,21 @@ class ExtendedImageGestureState extends State } if (_pageViewState != null && _pageViewState!.isDraging) { - _pageViewState!.onDragEnd(DragEndDetails( - velocity: details.velocity, - primaryVelocity: - _pageViewState!.widget.scrollDirection == Axis.horizontal - ? details.velocity.pixelsPerSecond.dx - : details.velocity.pixelsPerSecond.dy, - )); + _pageViewState!.onDragEnd( + DragEndDetails( + velocity: _pageViewState!.widget.scrollDirection == Axis.horizontal + ? Velocity( + pixelsPerSecond: + Offset(details.velocity.pixelsPerSecond.dx, 0)) + : Velocity( + pixelsPerSecond: + Offset(0, details.velocity.pixelsPerSecond.dy)), + primaryVelocity: + _pageViewState!.widget.scrollDirection == Axis.horizontal + ? details.velocity.pixelsPerSecond.dx + : details.velocity.pixelsPerSecond.dy, + ), + ); return; } diff --git a/lib/src/gesture/page_view/gesture_page_view.dart b/lib/src/gesture/page_view/gesture_page_view.dart index 999e39f7..654a4de3 100644 --- a/lib/src/gesture/page_view/gesture_page_view.dart +++ b/lib/src/gesture/page_view/gesture_page_view.dart @@ -1,11 +1,13 @@ import 'package:extended_image/extended_image.dart'; -import 'package:extended_image/src/gesture_detector/drag.dart'; + +import 'package:extended_image/src/gesture_detector/official.dart'; + import 'package:flutter/gestures.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +export 'page_controller/official.dart'; export 'rendering/sliver_fill.dart'; -export 'widgets/page_controller.dart'; export 'widgets/sliver_fill.dart'; part 'widgets/page_view.dart'; @@ -18,14 +20,17 @@ final ExtendedPageController _defaultPageController = ExtendedPageController(); const PageScrollPhysics _kPagePhysics = PageScrollPhysics(); const ScrollPhysics _defaultScrollPhysics = NeverScrollableScrollPhysics(); -final PageMetrics _testPageMetrics = PageMetrics( - axisDirection: AxisDirection.down, - minScrollExtent: 0, - maxScrollExtent: 10, - pixels: 5, - viewportDimension: 10, - viewportFraction: 1.0, -); +PageMetrics _getTestPageMetrics(BuildContext context) { + return PageMetrics( + axisDirection: AxisDirection.down, + minScrollExtent: 0, + maxScrollExtent: 10, + pixels: 5, + viewportDimension: 10, + viewportFraction: 1.0, + devicePixelRatio: View.of(context).devicePixelRatio, + ); +} /// whether should scoll page bool _defaultCanScrollPage(GestureDetails? gestureDetails) => true; @@ -189,6 +194,7 @@ class ExtendedImageGesturePageViewState @override void initState() { super.initState(); + _gestureAnimation = GestureAnimation(this, offsetCallBack: (Offset value) { final GestureDetails? gestureDetails = extendedImageGestureState?.gestureDetails; @@ -209,10 +215,10 @@ class ExtendedImageGesturePageViewState widget.controller.shouldIgnorePointerWhenScrolling) { bool canMove = true; - ///user's physics + // user's physics if (widget.physics.parent != null) { - canMove = - widget.physics.parent!.shouldAcceptUserOffset(_testPageMetrics); + canMove = widget.physics.parent! + .shouldAcceptUserOffset(_getTestPageMetrics(context)); } if (canMove) { switch (widget.scrollDirection) { @@ -325,7 +331,8 @@ class ExtendedImageGesturePageViewState ); if (widget.physics.parent == null || - widget.physics.parent!.shouldAcceptUserOffset(_testPageMetrics)) { + widget.physics.parent! + .shouldAcceptUserOffset(_getTestPageMetrics(context))) { result = RawGestureDetector( gestures: _gestureRecognizers, behavior: HitTestBehavior.opaque, diff --git a/lib/src/gesture/page_view/page_controller/official.dart b/lib/src/gesture/page_view/page_controller/official.dart new file mode 100644 index 00000000..73e800ea --- /dev/null +++ b/lib/src/gesture/page_view/page_controller/official.dart @@ -0,0 +1,369 @@ +import 'dart:math' as math; +import 'package:flutter/foundation.dart'; +import 'package:flutter/widgets.dart'; + +part 'page_position.dart'; +part 'page_controller.dart'; + +class _PageController extends ScrollController { + /// Creates a page controller. + /// + /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null. + _PageController({ + this.initialPage = 0, + this.keepPage = true, + this.viewportFraction = 1.0, + }) : assert(viewportFraction > 0.0); + + /// The page to show when first creating the [PageView]. + final int initialPage; + + /// Save the current [page] with [PageStorage] and restore it if + /// this controller's scrollable is recreated. + /// + /// If this property is set to false, the current [page] is never saved + /// and [initialPage] is always used to initialize the scroll offset. + /// If true (the default), the initial page is used the first time the + /// controller's scrollable is created, since there's isn't a page to + /// restore yet. Subsequently the saved page is restored and + /// [initialPage] is ignored. + /// + /// See also: + /// + /// * [PageStorageKey], which should be used when more than one + /// scrollable appears in the same route, to distinguish the [PageStorage] + /// locations used to save scroll offsets. + final bool keepPage; + + /// {@template flutter.widgets.pageview.viewportFraction} + /// The fraction of the viewport that each page should occupy. + /// + /// Defaults to 1.0, which means each page fills the viewport in the scrolling + /// direction. + /// {@endtemplate} + final double viewportFraction; + + /// The current page displayed in the controlled [PageView]. + /// + /// There are circumstances that this [_PageController] can't know the current + /// page. Reading [page] will throw an [AssertionError] in the following cases: + /// + /// 1. No [PageView] is currently using this [_PageController]. Once a + /// [PageView] starts using this [_PageController], the new [page] + /// position will be derived: + /// + /// * First, based on the attached [PageView]'s [BuildContext] and the + /// position saved at that context's [PageStorage] if [keepPage] is true. + /// * Second, from the [_PageController]'s [initialPage]. + /// + /// 2. More than one [PageView] using the same [_PageController]. + /// + /// The [hasClients] property can be used to check if a [PageView] is attached + /// prior to accessing [page]. + double? get page { + assert( + positions.isNotEmpty, + 'PageController.page cannot be accessed before a PageView is built with it.', + ); + assert( + positions.length == 1, + 'The page property cannot be read when multiple PageViews are attached to ' + 'the same PageController.', + ); + final _PagePosition position = this.position as _PagePosition; + return position.page; + } + + /// Animates the controlled [PageView] from the current page to the given page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + /// + /// The `duration` and `curve` arguments must not be null. + Future animateToPage( + int page, { + required Duration duration, + required Curve curve, + }) { + final _PagePosition position = this.position as _PagePosition; + if (position._cachedPage != null) { + position._cachedPage = page.toDouble(); + return Future.value(); + } + + return position.animateTo( + position.getPixelsFromPage(page.toDouble()), + duration: duration, + curve: curve, + ); + } + + /// Changes which page is displayed in the controlled [PageView]. + /// + /// Jumps the page position from its current value to the given value, + /// without animation, and without checking if the new value is in range. + void jumpToPage(int page) { + final _PagePosition position = this.position as _PagePosition; + if (position._cachedPage != null) { + position._cachedPage = page.toDouble(); + return; + } + + position.jumpTo(position.getPixelsFromPage(page.toDouble())); + } + + /// Animates the controlled [PageView] to the next page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + /// + /// The `duration` and `curve` arguments must not be null. + Future nextPage({required Duration duration, required Curve curve}) { + return animateToPage(page!.round() + 1, duration: duration, curve: curve); + } + + /// Animates the controlled [PageView] to the previous page. + /// + /// The animation lasts for the given duration and follows the given curve. + /// The returned [Future] resolves when the animation completes. + /// + /// The `duration` and `curve` arguments must not be null. + Future previousPage( + {required Duration duration, required Curve curve}) { + return animateToPage(page!.round() - 1, duration: duration, curve: curve); + } + + @override + ScrollPosition createScrollPosition(ScrollPhysics physics, + ScrollContext context, ScrollPosition? oldPosition) { + return _PagePosition( + physics: physics, + context: context, + initialPage: initialPage, + keepPage: keepPage, + viewportFraction: viewportFraction, + oldPosition: oldPosition, + ); + } + + @override + void attach(ScrollPosition position) { + super.attach(position); + final _PagePosition pagePosition = position as _PagePosition; + pagePosition.viewportFraction = viewportFraction; + } +} + +class _PagePosition extends ScrollPositionWithSingleContext + implements PageMetrics { + _PagePosition({ + required super.physics, + required super.context, + this.initialPage = 0, + bool keepPage = true, + double viewportFraction = 1.0, + super.oldPosition, + }) : assert(viewportFraction > 0.0), + _viewportFraction = viewportFraction, + _pageToUseOnStartup = initialPage.toDouble(), + super( + initialPixels: null, + keepScrollOffset: keepPage, + ); + + final int initialPage; + double _pageToUseOnStartup; + // When the viewport has a zero-size, the `page` can not + // be retrieved by `getPageFromPixels`, so we need to cache the page + // for use when resizing the viewport to non-zero next time. + double? _cachedPage; + + @override + Future ensureVisible( + RenderObject object, { + double alignment = 0.0, + Duration duration = Duration.zero, + Curve curve = Curves.ease, + ScrollPositionAlignmentPolicy alignmentPolicy = + ScrollPositionAlignmentPolicy.explicit, + RenderObject? targetRenderObject, + }) { + // Since the _PagePosition is intended to cover the available space within + // its viewport, stop trying to move the target render object to the center + // - otherwise, could end up changing which page is visible and moving the + // targetRenderObject out of the viewport. + return super.ensureVisible( + object, + alignment: alignment, + duration: duration, + curve: curve, + alignmentPolicy: alignmentPolicy, + ); + } + + @override + double get viewportFraction => _viewportFraction; + double _viewportFraction; + set viewportFraction(double value) { + if (_viewportFraction == value) { + return; + } + final double? oldPage = page; + _viewportFraction = value; + if (oldPage != null) { + forcePixels(getPixelsFromPage(oldPage)); + } + } + + // The amount of offset that will be added to [minScrollExtent] and subtracted + // from [maxScrollExtent], such that every page will properly snap to the center + // of the viewport when viewportFraction is greater than 1. + // + // The value is 0 if viewportFraction is less than or equal to 1, larger than 0 + // otherwise. + double get _initialPageOffset => + math.max(0, viewportDimension * (viewportFraction - 1) / 2); + + double getPageFromPixels(double pixels, double viewportDimension) { + assert(viewportDimension > 0.0); + final double actual = math.max(0.0, pixels - _initialPageOffset) / + (viewportDimension * viewportFraction); + final double round = actual.roundToDouble(); + if ((actual - round).abs() < precisionErrorTolerance) { + return round; + } + return actual; + } + + double getPixelsFromPage(double page) { + return page * viewportDimension * viewportFraction + _initialPageOffset; + } + + @override + double? get page { + assert( + !hasPixels || hasContentDimensions, + 'Page value is only available after content dimensions are established.', + ); + return !hasPixels || !hasContentDimensions + ? null + : _cachedPage ?? + getPageFromPixels( + clampDouble(pixels, minScrollExtent, maxScrollExtent), + viewportDimension); + } + + @override + void saveScrollOffset() { + PageStorage.maybeOf(context.storageContext)?.writeState( + context.storageContext, + _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); + } + + @override + void restoreScrollOffset() { + if (!hasPixels) { + final double? value = PageStorage.maybeOf(context.storageContext) + ?.readState(context.storageContext) as double?; + if (value != null) { + _pageToUseOnStartup = value; + } + } + } + + @override + void saveOffset() { + context.saveOffset( + _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); + } + + @override + void restoreOffset(double offset, {bool initialRestore = false}) { + if (initialRestore) { + _pageToUseOnStartup = offset; + } else { + jumpTo(getPixelsFromPage(offset)); + } + } + + // [_ExtendedPagePosition] has override this method + // We can't call super.super.applyViewportDimension in _ExtendedPagePosition + // so commont + // @override + // bool applyViewportDimension(double viewportDimension) { + // final double? oldViewportDimensions = + // hasViewportDimension ? this.viewportDimension : null; + // if (viewportDimension == oldViewportDimensions) { + // return true; + // } + // final bool result = super.applyViewportDimension(viewportDimension); + // final double? oldPixels = hasPixels ? pixels : null; + // double page; + // if (oldPixels == null) { + // page = _pageToUseOnStartup; + // } else if (oldViewportDimensions == 0.0) { + // // If resize from zero, we should use the _cachedPage to recover the state. + // page = _cachedPage!; + // } else { + // page = getPageFromPixels(oldPixels, oldViewportDimensions!); + // } + // final double newPixels = getPixelsFromPage(page); + + // // If the viewportDimension is zero, cache the page + // // in case the viewport is resized to be non-zero. + // _cachedPage = (viewportDimension == 0.0) ? page : null; + + // if (newPixels != oldPixels) { + // correctPixels(newPixels); + // return false; + // } + // return result; + // } + + @override + void absorb(ScrollPosition other) { + super.absorb(other); + assert(_cachedPage == null); + + if (other is! _PagePosition) { + return; + } + + if (other._cachedPage != null) { + _cachedPage = other._cachedPage; + } + } + + @override + bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { + final double newMinScrollExtent = minScrollExtent + _initialPageOffset; + return super.applyContentDimensions( + newMinScrollExtent, + math.max(newMinScrollExtent, maxScrollExtent - _initialPageOffset), + ); + } + + @override + PageMetrics copyWith({ + double? minScrollExtent, + double? maxScrollExtent, + double? pixels, + double? viewportDimension, + AxisDirection? axisDirection, + double? viewportFraction, + double? devicePixelRatio, + }) { + return PageMetrics( + minScrollExtent: minScrollExtent ?? + (hasContentDimensions ? this.minScrollExtent : null), + maxScrollExtent: maxScrollExtent ?? + (hasContentDimensions ? this.maxScrollExtent : null), + pixels: pixels ?? (hasPixels ? this.pixels : null), + viewportDimension: viewportDimension ?? + (hasViewportDimension ? this.viewportDimension : null), + axisDirection: axisDirection ?? this.axisDirection, + viewportFraction: viewportFraction ?? this.viewportFraction, + devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio, + ); + } +} diff --git a/lib/src/gesture/page_view/page_controller/page_controller.dart b/lib/src/gesture/page_view/page_controller/page_controller.dart new file mode 100644 index 00000000..463a86e2 --- /dev/null +++ b/lib/src/gesture/page_view/page_controller/page_controller.dart @@ -0,0 +1,65 @@ +part of 'official.dart'; + +class ExtendedPageController extends _PageController { + ExtendedPageController({ + super.initialPage = 0, + super.keepPage = true, + super.viewportFraction = 1.0, + this.shouldIgnorePointerWhenScrolling = false, + this.pageSpacing = 0.0, + }); + + /// Whether the contents of the widget should ignore [PointerEvent] inputs. + /// + /// Setting this value to true prevents the use from interacting with the + /// contents of the widget with pointer events. The widget itself is still + /// interactive. + /// + /// For example, if the scroll position is being driven by an animation, it + /// might be appropriate to set this value to ignore pointer events to + /// prevent the user from accidentally interacting with the contents of the + /// widget as it animates. The user will still be able to touch the widget, + /// potentially stopping the animation. + /// + /// + /// if true, we should handle scale event in [ExtendedImageGesturePageView] before [ExtendedImageGesturePageView] stop scroll. + /// notice: there is one issue that we may be zoom two image at the same time, because we can't find out which one should be zoomed. + /// + /// + /// if false, Image can accept scale event before [ExtendedImageGesturePageView] stop scroll. + /// notice: we don't know there are any issues if we don't ignore [PointerEvent] inputs when it's scrolling. + /// + /// + /// Two way to solve issue that we can's zoom image before [PageView] stop scroll. + /// + /// + /// default is false. + + final bool shouldIgnorePointerWhenScrolling; + + /// The number of logical pixels between each page. + + final double pageSpacing; + + @override + ScrollPosition createScrollPosition(ScrollPhysics physics, + ScrollContext context, ScrollPosition? oldPosition) { + return _ExtendedPagePosition( + physics: physics, + context: context, + initialPage: initialPage, + keepPage: keepPage, + viewportFraction: viewportFraction, + oldPosition: oldPosition, + pageSpacing: pageSpacing, + ); + } + + @override + void attach(ScrollPosition position) { + super.attach(position); + final _ExtendedPagePosition pagePosition = + position as _ExtendedPagePosition; + pagePosition.pageSpacing = pageSpacing; + } +} diff --git a/lib/src/gesture/page_view/page_controller/page_position.dart b/lib/src/gesture/page_view/page_controller/page_position.dart new file mode 100644 index 00000000..54822d34 --- /dev/null +++ b/lib/src/gesture/page_view/page_controller/page_position.dart @@ -0,0 +1,61 @@ +// ignore_for_file: prefer_final_fields, overridden_fields, always_put_control_body_on_new_line + +part of 'official.dart'; + +class _ExtendedPagePosition extends _PagePosition { + _ExtendedPagePosition({ + required super.physics, + required super.context, + super.initialPage = 0, + super.keepPage = true, + super.viewportFraction = 1.0, + super.oldPosition, + double pageSpacing = 0.0, + }) : _pageSpacing = pageSpacing; + double _pageSpacing; + double get pageSpacing => _pageSpacing; + set pageSpacing(double value) { + if (_pageSpacing != value) { + final double? oldPage = page; + _pageSpacing = value; + if (oldPage != null) forcePixels(getPixelsFromPage(oldPage)); + } + } + + // fix viewportDimension + @override + double get viewportDimension => super.viewportDimension + pageSpacing; + + @override + bool applyViewportDimension(double viewportDimension) { + final double? oldViewportDimensions = + // fix viewportDimension + hasViewportDimension ? this.viewportDimension - pageSpacing : null; + + if (viewportDimension == oldViewportDimensions) { + return true; + } + + final bool result = super.applyViewportDimension(viewportDimension); + final double? oldPixels = hasPixels ? pixels : null; + double page; + if (oldPixels == null) { + page = _pageToUseOnStartup; + } else if (oldViewportDimensions == 0.0) { + // If resize from zero, we should use the _cachedPage to recover the state. + page = _cachedPage!; + } else { + page = getPageFromPixels(oldPixels, oldViewportDimensions!); + } + final double newPixels = getPixelsFromPage(page); + + // If the viewportDimension is zero, cache the page + // in case the viewport is resized to be non-zero. + _cachedPage = (viewportDimension == 0.0) ? page : null; + if (newPixels != oldPixels) { + correctPixels(newPixels); + return false; + } + return result; + } +} diff --git a/lib/src/gesture/page_view/widgets/page_controller.dart b/lib/src/gesture/page_view/widgets/page_controller.dart deleted file mode 100644 index 17ea2d38..00000000 --- a/lib/src/gesture/page_view/widgets/page_controller.dart +++ /dev/null @@ -1,189 +0,0 @@ -// ignore_for_file: unnecessary_null_comparison, always_put_control_body_on_new_line -import 'dart:math' as math; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; - -part 'scroll_position.dart'; - -class ExtendedPageController extends ScrollController { - /// Creates a page controller. - /// - /// The [initialPage], [keepPage], and [viewportFraction] arguments must not be null. - ExtendedPageController({ - this.initialPage = 0, - this.keepPage = true, - this.viewportFraction = 1.0, - this.pageSpacing = 0.0, - this.shouldIgnorePointerWhenScrolling = false, - }) : assert(initialPage != null), - assert(keepPage != null), - assert(viewportFraction != null), - assert(viewportFraction > 0.0); - - /// Whether the contents of the widget should ignore [PointerEvent] inputs. - /// - /// Setting this value to true prevents the use from interacting with the - /// contents of the widget with pointer events. The widget itself is still - /// interactive. - /// - /// For example, if the scroll position is being driven by an animation, it - /// might be appropriate to set this value to ignore pointer events to - /// prevent the user from accidentally interacting with the contents of the - /// widget as it animates. The user will still be able to touch the widget, - /// potentially stopping the animation. - /// - /// - /// if true, we should handle scale event in [ExtendedImageGesturePageView] before [ExtendedImageGesturePageView] stop scroll. - /// notice: there is one issue that we may be zoom two image at the same time, because we can't find out which one should be zoomed. - /// - /// - /// if false, Image can accept scale event before [ExtendedImageGesturePageView] stop scroll. - /// notice: we don't know there are any issues if we don't ignore [PointerEvent] inputs when it's scrolling. - /// - /// - /// Two way to solve issue that we can's zoom image before [PageView] stop scroll. - /// - /// - /// default is false. - final bool shouldIgnorePointerWhenScrolling; - - /// The page to show when first creating the [PageView]. - final int initialPage; - - /// Save the current [page] with [PageStorage] and restore it if - /// this controller's scrollable is recreated. - /// - /// If this property is set to false, the current [page] is never saved - /// and [initialPage] is always used to initialize the scroll offset. - /// If true (the default), the initial page is used the first time the - /// controller's scrollable is created, since there's isn't a page to - /// restore yet. Subsequently the saved page is restored and - /// [initialPage] is ignored. - /// - /// See also: - /// - /// * [PageStorageKey], which should be used when more than one - /// scrollable appears in the same route, to distinguish the [PageStorage] - /// locations used to save scroll offsets. - final bool keepPage; - - /// The fraction of the viewport that each page should occupy. - /// - /// Defaults to 1.0, which means each page fills the viewport in the scrolling - /// direction. - final double viewportFraction; - - /// The number of logical pixels between each page. - final double pageSpacing; - - /// The current page displayed in the controlled [PageView]. - /// - /// There are circumstances that this [ExtendedPageController] can't know the current - /// page. Reading [page] will throw an [AssertionError] in the following cases: - /// - /// 1. No [PageView] is currently using this [ExtendedPageController]. Once a - /// [PageView] starts using this [ExtendedPageController], the new [page] - /// position will be derived: - /// - /// * First, based on the attached [PageView]'s [BuildContext] and the - /// position saved at that context's [PageStorage] if [keepPage] is true. - /// * Second, from the [ExtendedPageController]'s [initialPage]. - /// - /// 2. More than one [PageView] using the same [ExtendedPageController]. - /// - /// The [hasClients] property can be used to check if a [PageView] is attached - /// prior to accessing [page]. - double? get page { - assert( - positions.isNotEmpty, - 'PageController.page cannot be accessed before a PageView is built with it.', - ); - assert( - positions.length == 1, - 'The page property cannot be read when multiple PageViews are attached to ' - 'the same PageController.', - ); - final ExtendedPagePosition position = this.position as ExtendedPagePosition; - return position.page; - } - - /// Animates the controlled [PageView] from the current page to the given page. - /// - /// The animation lasts for the given duration and follows the given curve. - /// The returned [Future] resolves when the animation completes. - /// - /// The `duration` and `curve` arguments must not be null. - Future animateToPage( - int page, { - required Duration duration, - required Curve curve, - }) { - final ExtendedPagePosition position = this.position as ExtendedPagePosition; - if (position._cachedPage != null) { - position._cachedPage = page.toDouble(); - return Future.value(); - } - return position.animateTo( - position.getPixelsFromPage(page.toDouble()), - duration: duration, - curve: curve, - ); - } - - /// Changes which page is displayed in the controlled [PageView]. - /// - /// Jumps the page position from its current value to the given value, - /// without animation, and without checking if the new value is in range. - void jumpToPage(int page) { - final ExtendedPagePosition position = this.position as ExtendedPagePosition; - if (position._cachedPage != null) { - position._cachedPage = page.toDouble(); - return; - } - position.jumpTo(position.getPixelsFromPage(page.toDouble())); - } - - /// Animates the controlled [PageView] to the next page. - /// - /// The animation lasts for the given duration and follows the given curve. - /// The returned [Future] resolves when the animation completes. - /// - /// The `duration` and `curve` arguments must not be null. - Future nextPage({required Duration duration, required Curve curve}) { - return animateToPage(page!.round() + 1, duration: duration, curve: curve); - } - - /// Animates the controlled [PageView] to the previous page. - /// - /// The animation lasts for the given duration and follows the given curve. - /// The returned [Future] resolves when the animation completes. - /// - /// The `duration` and `curve` arguments must not be null. - Future previousPage( - {required Duration duration, required Curve curve}) { - return animateToPage(page!.round() - 1, duration: duration, curve: curve); - } - - @override - ScrollPosition createScrollPosition(ScrollPhysics physics, - ScrollContext context, ScrollPosition? oldPosition) { - return ExtendedPagePosition( - physics: physics, - context: context, - initialPage: initialPage, - keepPage: keepPage, - viewportFraction: viewportFraction, - oldPosition: oldPosition, - pageSpacing: pageSpacing, - ); - } - - @override - void attach(ScrollPosition position) { - super.attach(position); - final ExtendedPagePosition pagePosition = position as ExtendedPagePosition; - pagePosition.viewportFraction = viewportFraction; - pagePosition.pageSpacing = pageSpacing; - } -} diff --git a/lib/src/gesture/page_view/widgets/scroll_position.dart b/lib/src/gesture/page_view/widgets/scroll_position.dart deleted file mode 100644 index 55e9ce9e..00000000 --- a/lib/src/gesture/page_view/widgets/scroll_position.dart +++ /dev/null @@ -1,234 +0,0 @@ -part of 'page_controller.dart'; -// ignore_for_file: unnecessary_null_comparison, always_put_control_body_on_new_line - -class ExtendedPagePosition extends ScrollPositionWithSingleContext - implements PageMetrics { - ExtendedPagePosition({ - required ScrollPhysics physics, - required ScrollContext context, - this.initialPage = 0, - bool keepPage = true, - double viewportFraction = 1.0, - ScrollPosition? oldPosition, - double pageSpacing = 0.0, - }) : assert(initialPage != null), - assert(keepPage != null), - assert(viewportFraction != null), - assert(viewportFraction > 0.0), - assert(pageSpacing != null), - assert(pageSpacing >= 0.0), - _viewportFraction = viewportFraction, - _pageSpacing = pageSpacing, - _pageToUseOnStartup = initialPage.toDouble(), - super( - physics: physics, - context: context, - initialPixels: null, - keepScrollOffset: keepPage, - oldPosition: oldPosition, - ); - - final int initialPage; - double _pageToUseOnStartup; - // When the viewport has a zero-size, the `page` can not - // be retrieved by `getPageFromPixels`, so we need to cache the page - // for use when resizing the viewport to non-zero next time. - double? _cachedPage; - - @override - Future ensureVisible( - RenderObject object, { - double alignment = 0.0, - Duration duration = Duration.zero, - Curve curve = Curves.ease, - ScrollPositionAlignmentPolicy alignmentPolicy = - ScrollPositionAlignmentPolicy.explicit, - RenderObject? targetRenderObject, - }) { - // Since the _PagePosition is intended to cover the available space within - // its viewport, stop trying to move the target render object to the center - // - otherwise, could end up changing which page is visible and moving the - // targetRenderObject out of the viewport. - return super.ensureVisible( - object, - alignment: alignment, - duration: duration, - curve: curve, - alignmentPolicy: alignmentPolicy, - targetRenderObject: null, - ); - } - - @override - double get viewportFraction => _viewportFraction; - double _viewportFraction; - set viewportFraction(double value) { - if (_viewportFraction == value) return; - final double? oldPage = page; - _viewportFraction = value; - if (oldPage != null) forcePixels(getPixelsFromPage(oldPage)); - } - - double _pageSpacing; - double get pageSpacing => _pageSpacing; - set pageSpacing(double value) { - if (_pageSpacing != value) { - final double? oldPage = page; - _pageSpacing = value; - if (oldPage != null) forcePixels(getPixelsFromPage(oldPage)); - } - } - - // fix viewportDimension - @override - double get viewportDimension => super.viewportDimension + pageSpacing; - - // The amount of offset that will be added to [minScrollExtent] and subtracted - // from [maxScrollExtent], such that every page will properly snap to the center - // of the viewport when viewportFraction is greater than 1. - // - // The value is 0 if viewportFraction is less than or equal to 1, larger than 0 - // otherwise. - double get _initialPageOffset => - math.max(0, viewportDimension * (viewportFraction - 1) / 2); - - double getPageFromPixels(double pixels, double viewportDimension) { - final double actual = math.max(0.0, pixels - _initialPageOffset) / - math.max(1.0, viewportDimension * viewportFraction); - final double round = actual.roundToDouble(); - if ((actual - round).abs() < precisionErrorTolerance) { - return round; - } - return actual; - } - - double getPixelsFromPage(double page) { - return page * viewportDimension * viewportFraction + _initialPageOffset; - } - - @override - double? get page { - assert( - !hasPixels || hasContentDimensions, - 'Page value is only available after content dimensions are established.', - ); - return !hasPixels || !hasContentDimensions - ? null - : _cachedPage ?? - getPageFromPixels( - clampDouble(pixels, minScrollExtent, maxScrollExtent), - viewportDimension); - } - - @override - void saveScrollOffset() { - PageStorage.maybeOf(context.storageContext)?.writeState( - context.storageContext, - _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); - } - - @override - void restoreScrollOffset() { - if (!hasPixels) { - final double? value = PageStorage.maybeOf(context.storageContext) - ?.readState(context.storageContext) as double?; - if (value != null) { - _pageToUseOnStartup = value; - } - } - } - - @override - void saveOffset() { - context.saveOffset( - _cachedPage ?? getPageFromPixels(pixels, viewportDimension)); - } - - @override - void restoreOffset(double offset, {bool initialRestore = false}) { - assert(initialRestore != null); - assert(offset != null); - if (initialRestore) { - _pageToUseOnStartup = offset; - } else { - jumpTo(getPixelsFromPage(offset)); - } - } - - @override - bool applyViewportDimension(double viewportDimension) { - final double? oldViewportDimensions = - // fix viewportDimension - hasViewportDimension ? this.viewportDimension - pageSpacing : null; - - if (viewportDimension == oldViewportDimensions) { - return true; - } - final bool result = super.applyViewportDimension(viewportDimension); - final double? oldPixels = hasPixels ? pixels : null; - double page; - if (oldPixels == null) { - page = _pageToUseOnStartup; - } else if (oldViewportDimensions == 0.0) { - // If resize from zero, we should use the _cachedPage to recover the state. - page = _cachedPage!; - } else { - page = getPageFromPixels(oldPixels, oldViewportDimensions!); - } - final double newPixels = getPixelsFromPage(page); - - // If the viewportDimension is zero, cache the page - // in case the viewport is resized to be non-zero. - _cachedPage = (viewportDimension == 0.0) ? page : null; - if (newPixels != oldPixels) { - correctPixels(newPixels); - return false; - } - return result; - } - - @override - void absorb(ScrollPosition other) { - super.absorb(other); - assert(_cachedPage == null); - - if (other is! ExtendedPagePosition) { - return; - } - - if (other._cachedPage != null) { - _cachedPage = other._cachedPage; - } - } - - @override - bool applyContentDimensions(double minScrollExtent, double maxScrollExtent) { - final double newMinScrollExtent = minScrollExtent + _initialPageOffset; - return super.applyContentDimensions( - newMinScrollExtent, - math.max(newMinScrollExtent, maxScrollExtent - _initialPageOffset), - ); - } - - @override - PageMetrics copyWith({ - double? minScrollExtent, - double? maxScrollExtent, - double? pixels, - double? viewportDimension, - AxisDirection? axisDirection, - double? viewportFraction, - }) { - return PageMetrics( - minScrollExtent: minScrollExtent ?? - (hasContentDimensions ? this.minScrollExtent : null), - maxScrollExtent: maxScrollExtent ?? - (hasContentDimensions ? this.maxScrollExtent : null), - pixels: pixels ?? (hasPixels ? this.pixels : null), - viewportDimension: viewportDimension ?? - (hasViewportDimension ? this.viewportDimension : null), - axisDirection: axisDirection ?? this.axisDirection, - viewportFraction: viewportFraction ?? this.viewportFraction, - ); - } -} diff --git a/lib/src/gesture_detector/drag_gesture_recognizer.dart b/lib/src/gesture_detector/drag_gesture_recognizer.dart new file mode 100644 index 00000000..a7ba8796 --- /dev/null +++ b/lib/src/gesture_detector/drag_gesture_recognizer.dart @@ -0,0 +1,147 @@ +part of 'official.dart'; + +typedef CanHorizontalOrVerticalDrag = bool Function(); + +mixin DragGestureRecognizerMixin on _DragGestureRecognizer { + bool get canDrag => + canHorizontalOrVerticalDrag == null || canHorizontalOrVerticalDrag!(); + + bool _shouldAccpet() { + if (!canDrag) { + return false; + } + if (_velocityTrackers.keys.length == 1) { + return true; + } + + // if pointers are not the only, check whether they are in the negative + // maybe this is a Horizontal/Vertical zoom + Offset offset = const Offset(1, 1); + for (final VelocityTracker tracker in _velocityTrackers.values) { + if (tracker is ExtendedVelocityTracker) { + final Offset delta = tracker.getSamplesDelta(); + offset = Offset(offset.dx * (delta.dx == 0 ? 1 : delta.dx), + offset.dy * (delta.dy == 0 ? 1 : delta.dy)); + } + } + + return !(offset.dx < 0 || offset.dy < 0); + } + + CanHorizontalOrVerticalDrag? get canHorizontalOrVerticalDrag; + + @override + void handleEvent(PointerEvent event) { + assert(_state != _DragState.ready); + if (!event.synthesized && + (event is PointerDownEvent || + event is PointerMoveEvent || + event is PointerPanZoomStartEvent || + event is PointerPanZoomUpdateEvent)) { + final VelocityTracker tracker = _velocityTrackers[event.pointer]!; + if (event is PointerPanZoomStartEvent) { + tracker.addPosition(event.timeStamp, Offset.zero); + } else if (event is PointerPanZoomUpdateEvent) { + tracker.addPosition(event.timeStamp, event.pan); + } else { + tracker.addPosition(event.timeStamp, event.localPosition); + } + } + if (event is PointerMoveEvent && event.buttons != _initialButtons) { + _giveUpPointer(event.pointer); + return; + } + if (event is PointerMoveEvent || event is PointerPanZoomUpdateEvent) { + final Offset delta = (event is PointerMoveEvent) + ? event.delta + : (event as PointerPanZoomUpdateEvent).panDelta; + final Offset localDelta = (event is PointerMoveEvent) + ? event.localDelta + : (event as PointerPanZoomUpdateEvent).localPanDelta; + final Offset position = (event is PointerMoveEvent) + ? event.position + : (event.position + (event as PointerPanZoomUpdateEvent).pan); + final Offset localPosition = (event is PointerMoveEvent) + ? event.localPosition + : (event.localPosition + + (event as PointerPanZoomUpdateEvent).localPan); + if (_state == _DragState.accepted) { + _checkUpdate( + sourceTimeStamp: event.timeStamp, + delta: _getDeltaForDetails(localDelta), + primaryDelta: _getPrimaryValueFromOffset(localDelta), + globalPosition: position, + localPosition: localPosition, + ); + } else { + _pendingDragOffset += OffsetPair(local: localDelta, global: delta); + _lastPendingEventTimestamp = event.timeStamp; + _lastTransform = event.transform; + final Offset movedLocally = _getDeltaForDetails(localDelta); + final Matrix4? localToGlobalTransform = event.transform == null + ? null + : Matrix4.tryInvert(event.transform!); + _globalDistanceMoved += PointerEvent.transformDeltaViaPositions( + transform: localToGlobalTransform, + untransformedDelta: movedLocally, + untransformedEndPosition: localPosition) + .distance * + (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign; + if (_hasSufficientGlobalDistanceToAccept( + event.kind, gestureSettings?.touchSlop) && + // zmtzawqlp + _shouldAccpet()) { + resolve(GestureDisposition.accepted); + } + } + } + if (event is PointerUpEvent || + event is PointerCancelEvent || + event is PointerPanZoomEndEvent) { + _giveUpPointer(event.pointer); + } + } +} + +abstract class ExtendedDragGestureRecognizer extends _DragGestureRecognizer + with DragGestureRecognizerMixin { + ExtendedDragGestureRecognizer({ + super.debugOwner, + super.dragStartBehavior = DragStartBehavior.start, + super.velocityTrackerBuilder = _defaultBuilder, + super.supportedDevices, + super.allowedButtonsFilter, + this.canHorizontalOrVerticalDrag, + }); + + static ExtendedVelocityTracker _defaultBuilder(PointerEvent event) => + ExtendedVelocityTracker.withKind(event.kind); + @override + final CanHorizontalOrVerticalDrag? canHorizontalOrVerticalDrag; +} + +class ExtendedHorizontalDragGestureRecognizer + extends _HorizontalDragGestureRecognizer with DragGestureRecognizerMixin { + ExtendedHorizontalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + this.canHorizontalOrVerticalDrag, + }); + + @override + final CanHorizontalOrVerticalDrag? canHorizontalOrVerticalDrag; +} + +class ExtendedVerticalDragGestureRecognizer + extends _VerticalDragGestureRecognizer with DragGestureRecognizerMixin { + ExtendedVerticalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + this.canHorizontalOrVerticalDrag, + }); + + @override + final CanHorizontalOrVerticalDrag? canHorizontalOrVerticalDrag; +} diff --git a/lib/src/gesture_detector/drag_gesture_recognizer_mixin.dart b/lib/src/gesture_detector/drag_gesture_recognizer_mixin.dart deleted file mode 100644 index 386b1e51..00000000 --- a/lib/src/gesture_detector/drag_gesture_recognizer_mixin.dart +++ /dev/null @@ -1,32 +0,0 @@ -part of 'drag.dart'; - -typedef CanHorizontalOrVerticalDrag = bool Function(); -mixin DragGestureRecognizerMixin { - bool get canDrag => - canHorizontalOrVerticalDrag == null || canHorizontalOrVerticalDrag!(); - Map get _velocityTrackers; - - bool _shouldAccpet() { - if (!canDrag) { - return false; - } - if (_velocityTrackers.keys.length == 1) { - return true; - } - - // if pointers are not the only, check whether they are in the negative - // maybe this is a Horizontal/Vertical zoom - Offset offset = const Offset(1, 1); - for (final VelocityTracker tracker in _velocityTrackers.values) { - if (tracker is ExtendedVelocityTracker) { - final Offset delta = tracker.getSamplesDelta(); - offset = Offset(offset.dx * (delta.dx == 0 ? 1 : delta.dx), - offset.dy * (delta.dy == 0 ? 1 : delta.dy)); - } - } - - return !(offset.dx < 0 || offset.dy < 0); - } - - CanHorizontalOrVerticalDrag? get canHorizontalOrVerticalDrag; -} diff --git a/lib/src/gesture_detector/drag.dart b/lib/src/gesture_detector/official.dart similarity index 63% rename from lib/src/gesture_detector/drag.dart rename to lib/src/gesture_detector/official.dart index 3a211683..c187c832 100644 --- a/lib/src/gesture_detector/drag.dart +++ b/lib/src/gesture_detector/official.dart @@ -1,9 +1,10 @@ -// ignore_for_file: unnecessary_null_comparison +// ignore_for_file: overridden_fields import 'package:flutter/foundation.dart'; import 'package:flutter/gestures.dart'; -import 'velocity_tracker.dart'; -part 'drag_gesture_recognizer_mixin.dart'; + +part 'drag_gesture_recognizer.dart'; +part 'velocity_tracker.dart'; enum _DragState { ready, @@ -11,155 +12,47 @@ enum _DragState { accepted, } -/// Recognizes movement in the vertical direction. -/// -/// Used for vertical scrolling. -/// -/// See also: -/// -/// * [ExtendedHorizontalDragGestureRecognizer], for a similar recognizer but for -/// horizontal movement. -/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that -/// track each touch point independently. -class ExtendedVerticalDragGestureRecognizer - extends ExtendedDragGestureRecognizer { - /// Create a gesture recognizer for interactions in the vertical axis. - /// - /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} - ExtendedVerticalDragGestureRecognizer({ - super.debugOwner, - @Deprecated( - 'Migrate to supportedDevices. ' - 'This feature was deprecated after v2.3.0-1.0.pre.', - ) - super.kind, - super.supportedDevices, - super.canHorizontalOrVerticalDrag, - }); - - @override - bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) { - final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; - final double minDistance = - minFlingDistance ?? computeHitSlop(kind, gestureSettings); - return estimate.pixelsPerSecond.dy.abs() > minVelocity && - estimate.offset.dy.abs() > minDistance; - } - - @override - bool _hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) { - return _globalDistanceMoved.abs() > - computeHitSlop(pointerDeviceKind, gestureSettings); - } - - @override - Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy); - - @override - double _getPrimaryValueFromOffset(Offset value) => value.dy; - - @override - String get debugDescription => 'vertical drag'; -} - -/// Recognizes movement in the horizontal direction. -/// -/// Used for horizontal scrolling. -/// -/// See also: -/// -/// * [VerticalDragGestureRecognizer], for a similar recognizer but for -/// vertical movement. -/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that -/// track each touch point independently. -class ExtendedHorizontalDragGestureRecognizer - extends ExtendedDragGestureRecognizer { - /// Create a gesture recognizer for interactions in the horizontal axis. - /// - /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} - ExtendedHorizontalDragGestureRecognizer({ - super.debugOwner, - @Deprecated( - 'Migrate to supportedDevices. ' - 'This feature was deprecated after v2.3.0-1.0.pre.', - ) - super.kind, - super.supportedDevices, - super.canHorizontalOrVerticalDrag, - }); - - @override - bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) { - final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; - final double minDistance = - minFlingDistance ?? computeHitSlop(kind, gestureSettings); - return estimate.pixelsPerSecond.dx.abs() > minVelocity && - estimate.offset.dx.abs() > minDistance; - } - - @override - bool _hasSufficientGlobalDistanceToAccept( - PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) { - return _globalDistanceMoved.abs() > - computeHitSlop(pointerDeviceKind, gestureSettings); - } - - @override - Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0); - - @override - double _getPrimaryValueFromOffset(Offset value) => value.dx; - - @override - String get debugDescription => 'horizontal drag'; -} - /// Recognizes movement. /// -/// In contrast to [MultiDragGestureRecognizer], [ExtendedDragGestureRecognizer] +/// In contrast to [MultiDragGestureRecognizer], [DragGestureRecognizer] /// recognizes a single gesture sequence for all the pointers it watches, which /// means that the recognizer has at most one drag sequence active at any given /// time regardless of how many pointers are in contact with the screen. /// -/// [ExtendedDragGestureRecognizer] is not intended to be used directly. Instead, +/// [DragGestureRecognizer] is not intended to be used directly. Instead, /// consider using one of its subclasses to recognize specific types for drag /// gestures. /// -/// [ExtendedDragGestureRecognizer] competes on pointer events of [kPrimaryButton] -/// only when it has at least one non-null callback. If it has no callbacks, it -/// is a no-op. +/// [DragGestureRecognizer] competes on pointer events only when it has at +/// least one non-null callback. If it has no callbacks, it is a no-op. /// /// See also: /// -/// * [ExtendedHorizontalDragGestureRecognizer], for left and right drags. -/// * [VerticalDragGestureRecognizer], for up and down drags. +/// * [_HorizontalDragGestureRecognizer], for left and right drags. +/// * [_VerticalDragGestureRecognizer], for up and down drags. /// * [PanGestureRecognizer], for drags that are not locked to a single axis. -abstract class ExtendedDragGestureRecognizer - extends OneSequenceGestureRecognizer with DragGestureRecognizerMixin { +abstract class _DragGestureRecognizer extends OneSequenceGestureRecognizer { /// Initialize the object. /// /// [dragStartBehavior] must not be null. /// /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} - ExtendedDragGestureRecognizer({ + _DragGestureRecognizer({ super.debugOwner, - @Deprecated( - 'Migrate to supportedDevices. ' - 'This feature was deprecated after v2.3.0-1.0.pre.', - ) - super.kind, this.dragStartBehavior = DragStartBehavior.start, this.velocityTrackerBuilder = _defaultBuilder, super.supportedDevices, - this.canHorizontalOrVerticalDrag, - }) : assert(dragStartBehavior != null); + AllowedButtonsFilter? allowedButtonsFilter, + }) : super( + allowedButtonsFilter: + allowedButtonsFilter ?? _defaultButtonAcceptBehavior); - @override - final CanHorizontalOrVerticalDrag? canHorizontalOrVerticalDrag; + static _VelocityTracker _defaultBuilder(PointerEvent event) => + _VelocityTracker.withKind(event.kind); - static ExtendedVelocityTracker _defaultBuilder(PointerEvent event) => - ExtendedVelocityTracker.withKind(event.kind); + // Accept the input if, and only if, [kPrimaryButton] is pressed. + static bool _defaultButtonAcceptBehavior(int buttons) => + buttons == kPrimaryButton; /// Configure the behavior of offsets passed to [onStart]. /// @@ -177,10 +70,10 @@ abstract class ExtendedDragGestureRecognizer /// /// ## Example: /// - /// A [ExtendedHorizontalDragGestureRecognizer] and a [VerticalDragGestureRecognizer] + /// A [_HorizontalDragGestureRecognizer] and a [_VerticalDragGestureRecognizer] /// compete with each other. A finger presses down on the screen with /// offset (500.0, 500.0), and then moves to position (510.0, 500.0) before - /// the [ExtendedHorizontalDragGestureRecognizer] wins the arena. With + /// the [_HorizontalDragGestureRecognizer] wins the arena. With /// [dragStartBehavior] set to [DragStartBehavior.down], the [onStart] /// callback will be called with position (500.0, 500.0). If it is /// instead set to [DragStartBehavior.start], [onStart] will be called with @@ -195,12 +88,14 @@ abstract class ExtendedDragGestureRecognizer /// /// See also: /// - /// * [kPrimaryButton], the button this callback responds to. + /// * [allowedButtonsFilter], which decides which button will be allowed. /// * [DragDownDetails], which is passed as an argument to this callback. GestureDragDownCallback? onDown; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onStart} /// A pointer has contacted the screen with a primary button and has begun to /// move. + /// {@endtemplate} /// /// The position of the pointer is provided in the callback's `details` /// argument, which is a [DragStartDetails] object. The [dragStartBehavior] @@ -208,32 +103,52 @@ abstract class ExtendedDragGestureRecognizer /// /// See also: /// - /// * [kPrimaryButton], the button this callback responds to. + /// * [allowedButtonsFilter], which decides which button will be allowed. /// * [DragStartDetails], which is passed as an argument to this callback. GestureDragStartCallback? onStart; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onUpdate} /// A pointer that is in contact with the screen with a primary button and /// moving has moved again. + /// {@endtemplate} /// /// The distance traveled by the pointer since the last update is provided in /// the callback's `details` argument, which is a [DragUpdateDetails] object. /// + /// If this gesture recognizer recognizes movement on a single axis (a + /// [_VerticalDragGestureRecognizer] or [_HorizontalDragGestureRecognizer]), + /// then `details` will reflect movement only on that axis and its + /// [DragUpdateDetails.primaryDelta] will be non-null. + /// If this gesture recognizer recognizes movement in all directions + /// (a [PanGestureRecognizer]), then `details` will reflect movement on + /// both axes and its [DragUpdateDetails.primaryDelta] will be null. + /// /// See also: /// - /// * [kPrimaryButton], the button this callback responds to. + /// * [allowedButtonsFilter], which decides which button will be allowed. /// * [DragUpdateDetails], which is passed as an argument to this callback. GestureDragUpdateCallback? onUpdate; + /// {@template flutter.gestures.monodrag.DragGestureRecognizer.onEnd} /// A pointer that was previously in contact with the screen with a primary /// button and moving is no longer in contact with the screen and was moving /// at a specific velocity when it stopped contacting the screen. + /// {@endtemplate} /// /// The velocity is provided in the callback's `details` argument, which is a /// [DragEndDetails] object. /// + /// If this gesture recognizer recognizes movement on a single axis (a + /// [_VerticalDragGestureRecognizer] or [_HorizontalDragGestureRecognizer]), + /// then `details` will reflect movement only on that axis and its + /// [DragEndDetails.primaryVelocity] will be non-null. + /// If this gesture recognizer recognizes movement in all directions + /// (a [PanGestureRecognizer]), then `details` will reflect movement on + /// both axes and its [DragEndDetails.primaryVelocity] will be null. + /// /// See also: /// - /// * [kPrimaryButton], the button this callback responds to. + /// * [allowedButtonsFilter], which decides which button will be allowed. /// * [DragEndDetails], which is passed as an argument to this callback. GestureDragEndCallback? onEnd; @@ -241,10 +156,10 @@ abstract class ExtendedDragGestureRecognizer /// /// See also: /// - /// * [kPrimaryButton], the button this callback responds to. + /// * [allowedButtonsFilter], which decides which button will be allowed. GestureDragCancelCallback? onCancel; - /// The minimum distance an input pointer drag must have moved to + /// The minimum distance an input pointer drag must have moved /// to be considered a fling gesture. /// /// This value is typically compared with the distance traveled along the @@ -266,7 +181,7 @@ abstract class ExtendedDragGestureRecognizer /// Determines the type of velocity estimation method to use for a potential /// drag gesture, when a new pointer is added. /// - /// To estimate the velocity of a gesture, [ExtendedDragGestureRecognizer] calls + /// To estimate the velocity of a gesture, [DragGestureRecognizer] calls /// [velocityTrackerBuilder] when it starts to track a new pointer in /// [addAllowedPointer], and add subsequent updates on the pointer to the /// resulting velocity tracker, until the gesture recognizer stops tracking @@ -275,11 +190,11 @@ abstract class ExtendedDragGestureRecognizer /// tracker this [GestureVelocityTrackerBuilder] returns. /// /// If left unspecified the default [velocityTrackerBuilder] creates a new - /// [VelocityTracker] for every pointer added. + /// [_VelocityTracker] for every pointer added. /// /// See also: /// - /// * [VelocityTracker], a velocity tracker that uses least squares estimation + /// * [_VelocityTracker], a velocity tracker that uses least squares estimation /// on the 20 most recent pointer data samples. It's a well-rounded velocity /// tracker and is used by default. /// * [IOSScrollViewFlingVelocityTracker], a specialized velocity tracker for @@ -291,6 +206,24 @@ abstract class ExtendedDragGestureRecognizer late OffsetPair _initialPosition; late OffsetPair _pendingDragOffset; Duration? _lastPendingEventTimestamp; + + /// When asserts are enabled, returns the last tracked pending event timestamp + /// for this recognizer. + /// + /// Otherwise, returns null. + /// + /// This getter is intended for use in framework unit tests. Applications must + /// not depend on its value. + @visibleForTesting + Duration? get debugLastPendingEventTimestamp { + Duration? lastPendingEventTimestamp; + assert(() { + lastPendingEventTimestamp = _lastPendingEventTimestamp; + return true; + }()); + return lastPendingEventTimestamp; + } + // The buttons sent by `PointerDownEvent`. If a `PointerMoveEvent` comes with a // different set of buttons, the gesture is canceled. int? _initialButtons; @@ -309,29 +242,30 @@ abstract class ExtendedDragGestureRecognizer /// inertia, for example. bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind); + /// Determines if a gesture is a fling or not, and if so its effective velocity. + /// + /// A fling calls its gesture end callback with a velocity, allowing the + /// provider of the callback to respond by carrying the gesture forward with + /// inertia, for example. + DragEndDetails? _considerFling( + VelocityEstimate estimate, PointerDeviceKind kind); + Offset _getDeltaForDetails(Offset delta); double? _getPrimaryValueFromOffset(Offset value); bool _hasSufficientGlobalDistanceToAccept( PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop); - @override final Map _velocityTrackers = {}; @override bool isPointerAllowed(PointerEvent event) { if (_initialButtons == null) { - switch (event.buttons) { - case kPrimaryButton: - if (onDown == null && - onStart == null && - onUpdate == null && - onEnd == null && - onCancel == null) { - return false; - } - break; - default: - return false; + if (onDown == null && + onStart == null && + onUpdate == null && + onEnd == null && + onCancel == null) { + return false; } } else { // There can be multiple drags simultaneously. Their effects are combined. @@ -386,7 +320,6 @@ abstract class ExtendedDragGestureRecognizer event is PointerPanZoomStartEvent || event is PointerPanZoomUpdateEvent)) { final VelocityTracker tracker = _velocityTrackers[event.pointer]!; - assert(tracker != null); if (event is PointerPanZoomStartEvent) { tracker.addPosition(event.timeStamp, Offset.zero); } else if (event is PointerPanZoomUpdateEvent) { @@ -436,9 +369,7 @@ abstract class ExtendedDragGestureRecognizer .distance * (_getPrimaryValueFromOffset(movedLocally) ?? 1).sign; if (_hasSufficientGlobalDistanceToAccept( - event.kind, gestureSettings?.touchSlop) && - // zmtzawqlp - _shouldAccpet()) { + event.kind, gestureSettings?.touchSlop)) { resolve(GestureDisposition.accepted); } } @@ -459,7 +390,7 @@ abstract class ExtendedDragGestureRecognizer if (_state != _DragState.accepted) { _state = _DragState.accepted; final OffsetPair delta = _pendingDragOffset; - final Duration timestamp = _lastPendingEventTimestamp!; + final Duration? timestamp = _lastPendingEventTimestamp; final Matrix4? transform = _lastTransform; final Offset localUpdateDelta; switch (dragStartBehavior) { @@ -521,7 +452,6 @@ abstract class ExtendedDragGestureRecognizer resolve(GestureDisposition.rejected); _checkCancel(); break; - case _DragState.accepted: _checkEnd(pointer); break; @@ -541,7 +471,6 @@ abstract class ExtendedDragGestureRecognizer } void _checkDown() { - assert(_initialButtons == kPrimaryButton); if (onDown != null) { final DragDownDetails details = DragDownDetails( globalPosition: _initialPosition.global, @@ -551,8 +480,7 @@ abstract class ExtendedDragGestureRecognizer } } - void _checkStart(Duration timestamp, int pointer) { - assert(_initialButtons == kPrimaryButton); + void _checkStart(Duration? timestamp, int pointer) { if (onStart != null) { final DragStartDetails details = DragStartDetails( sourceTimeStamp: timestamp, @@ -571,7 +499,6 @@ abstract class ExtendedDragGestureRecognizer required Offset globalPosition, Offset? localPosition, }) { - assert(_initialButtons == kPrimaryButton); if (onUpdate != null) { final DragUpdateDetails details = DragUpdateDetails( sourceTimeStamp: sourceTimeStamp, @@ -585,47 +512,30 @@ abstract class ExtendedDragGestureRecognizer } void _checkEnd(int pointer) { - assert(_initialButtons == kPrimaryButton); if (onEnd == null) { return; } final VelocityTracker tracker = _velocityTrackers[pointer]!; - assert(tracker != null); + final VelocityEstimate? estimate = tracker.getVelocityEstimate(); - final DragEndDetails details; + DragEndDetails? details; final String Function() debugReport; - - final VelocityEstimate? estimate = tracker.getVelocityEstimate(); - if (estimate != null && isFlingGesture(estimate, tracker.kind)) { - final Velocity velocity = - Velocity(pixelsPerSecond: estimate.pixelsPerSecond).clampMagnitude( - minFlingVelocity ?? kMinFlingVelocity, - maxFlingVelocity ?? kMaxFlingVelocity); - details = DragEndDetails( - velocity: velocity, - primaryVelocity: _getPrimaryValueFromOffset(velocity.pixelsPerSecond), - ); - debugReport = () { - return '$estimate; fling at $velocity.'; - }; + if (estimate == null) { + debugReport = () => 'Could not estimate velocity.'; } else { - details = DragEndDetails( - primaryVelocity: 0.0, - ); - debugReport = () { - if (estimate == null) { - return 'Could not estimate velocity.'; - } - return '$estimate; judged to not be a fling.'; - }; + details = _considerFling(estimate, tracker.kind); + debugReport = (details != null) + ? () => '$estimate; fling at ${details!.velocity}.' + : () => '$estimate; judged to not be a fling.'; } - invokeCallback('onEnd', () => onEnd!(details), + details ??= DragEndDetails(primaryVelocity: 0.0); + + invokeCallback('onEnd', () => onEnd!(details!), debugReport: debugReport); } void _checkCancel() { - assert(_initialButtons == kPrimaryButton); if (onCancel != null) { invokeCallback('onCancel', onCancel!); } @@ -644,3 +554,272 @@ abstract class ExtendedDragGestureRecognizer EnumProperty('start behavior', dragStartBehavior)); } } + +/// Recognizes movement in the vertical direction. +/// +/// Used for vertical scrolling. +/// +/// See also: +/// +/// * [_HorizontalDragGestureRecognizer], for a similar recognizer but for +/// horizontal movement. +/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that +/// track each touch point independently. +class _VerticalDragGestureRecognizer extends _DragGestureRecognizer { + /// Create a gesture recognizer for interactions in the vertical axis. + /// + /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} + _VerticalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + }); + + @override + bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) { + final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; + final double minDistance = + minFlingDistance ?? computeHitSlop(kind, gestureSettings); + return estimate.pixelsPerSecond.dy.abs() > minVelocity && + estimate.offset.dy.abs() > minDistance; + } + + @override + DragEndDetails? _considerFling( + VelocityEstimate estimate, PointerDeviceKind kind) { + if (!isFlingGesture(estimate, kind)) { + return null; + } + final double maxVelocity = maxFlingVelocity ?? kMaxFlingVelocity; + final double dy = + clampDouble(estimate.pixelsPerSecond.dy, -maxVelocity, maxVelocity); + return DragEndDetails( + velocity: Velocity(pixelsPerSecond: Offset(0, dy)), + primaryVelocity: dy, + ); + } + + @override + bool _hasSufficientGlobalDistanceToAccept( + PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) { + return _globalDistanceMoved.abs() > + computeHitSlop(pointerDeviceKind, gestureSettings); + } + + @override + Offset _getDeltaForDetails(Offset delta) => Offset(0.0, delta.dy); + + @override + double _getPrimaryValueFromOffset(Offset value) => value.dy; + + @override + String get debugDescription => 'vertical drag'; +} + +/// Recognizes movement in the horizontal direction. +/// +/// Used for horizontal scrolling. +/// +/// See also: +/// +/// * [_VerticalDragGestureRecognizer], for a similar recognizer but for +/// vertical movement. +/// * [MultiDragGestureRecognizer], for a family of gesture recognizers that +/// track each touch point independently. +class _HorizontalDragGestureRecognizer extends _DragGestureRecognizer { + /// Create a gesture recognizer for interactions in the horizontal axis. + /// + /// {@macro flutter.gestures.GestureRecognizer.supportedDevices} + _HorizontalDragGestureRecognizer({ + super.debugOwner, + super.supportedDevices, + super.allowedButtonsFilter, + }); + + @override + bool isFlingGesture(VelocityEstimate estimate, PointerDeviceKind kind) { + final double minVelocity = minFlingVelocity ?? kMinFlingVelocity; + final double minDistance = + minFlingDistance ?? computeHitSlop(kind, gestureSettings); + return estimate.pixelsPerSecond.dx.abs() > minVelocity && + estimate.offset.dx.abs() > minDistance; + } + + @override + DragEndDetails? _considerFling( + VelocityEstimate estimate, PointerDeviceKind kind) { + if (!isFlingGesture(estimate, kind)) { + return null; + } + final double maxVelocity = maxFlingVelocity ?? kMaxFlingVelocity; + final double dx = + clampDouble(estimate.pixelsPerSecond.dx, -maxVelocity, maxVelocity); + return DragEndDetails( + velocity: Velocity(pixelsPerSecond: Offset(dx, 0)), + primaryVelocity: dx, + ); + } + + @override + bool _hasSufficientGlobalDistanceToAccept( + PointerDeviceKind pointerDeviceKind, double? deviceTouchSlop) { + return _globalDistanceMoved.abs() > + computeHitSlop(pointerDeviceKind, gestureSettings); + } + + @override + Offset _getDeltaForDetails(Offset delta) => Offset(delta.dx, 0.0); + + @override + double _getPrimaryValueFromOffset(Offset value) => value.dx; + + @override + String get debugDescription => 'horizontal drag'; +} + +class _PointAtTime { + const _PointAtTime(this.point, this.time); + + final Duration time; + final Offset point; + + @override + String toString() => '_PointAtTime($point at $time)'; +} + +// Computes a pointer's velocity based on data from [PointerMoveEvent]s. +/// +/// The input data is provided by calling [addPosition]. Adding data is cheap. +/// +/// To obtain a velocity, call [getVelocity] or [getVelocityEstimate]. This will +/// compute the velocity based on the data added so far. Only call these when +/// you need to use the velocity, as they are comparatively expensive. +/// +/// The quality of the velocity estimation will be better if more data points +/// have been received. +class _VelocityTracker extends VelocityTracker { + /// Create a new velocity tracker for a pointer [kind]. + _VelocityTracker.withKind(this.kind) : super.withKind(kind); + + static const int _assumePointerMoveStoppedMilliseconds = 40; + static const int _historySize = 20; + static const int _horizonMilliseconds = 100; + static const int _minSampleSize = 3; + + /// The kind of pointer this tracker is for. + @override + final PointerDeviceKind kind; + + // Circular buffer; current sample at _index. + final List<_PointAtTime?> _samples = + List<_PointAtTime?>.filled(_historySize, null); + int _index = 0; + + /// Adds a position as the given time to the tracker. + @override + void addPosition(Duration time, Offset position) { + _index += 1; + if (_index == _historySize) { + _index = 0; + } + _samples[_index] = _PointAtTime(position, time); + } + + /// Returns an estimate of the velocity of the object being tracked by the + /// tracker given the current information available to the tracker. + /// + /// Information is added using [addPosition]. + /// + /// Returns null if there is no data on which to base an estimate. + @override + VelocityEstimate? getVelocityEstimate() { + final List x = []; + final List y = []; + final List w = []; + final List time = []; + int sampleCount = 0; + int index = _index; + + final _PointAtTime? newestSample = _samples[index]; + if (newestSample == null) { + return null; + } + + _PointAtTime previousSample = newestSample; + _PointAtTime oldestSample = newestSample; + + // Starting with the most recent PointAtTime sample, iterate backwards while + // the samples represent continuous motion. + do { + final _PointAtTime? sample = _samples[index]; + if (sample == null) { + break; + } + + final double age = + (newestSample.time - sample.time).inMicroseconds.toDouble() / 1000; + final double delta = + (sample.time - previousSample.time).inMicroseconds.abs().toDouble() / + 1000; + previousSample = sample; + if (age > _horizonMilliseconds || + delta > _assumePointerMoveStoppedMilliseconds) { + break; + } + + oldestSample = sample; + final Offset position = sample.point; + x.add(position.dx); + y.add(position.dy); + w.add(1.0); + time.add(-age); + index = (index == 0 ? _historySize : index) - 1; + + sampleCount += 1; + } while (sampleCount < _historySize); + + if (sampleCount >= _minSampleSize) { + final LeastSquaresSolver xSolver = LeastSquaresSolver(time, x, w); + final PolynomialFit? xFit = xSolver.solve(2); + if (xFit != null) { + final LeastSquaresSolver ySolver = LeastSquaresSolver(time, y, w); + final PolynomialFit? yFit = ySolver.solve(2); + if (yFit != null) { + return VelocityEstimate( + // convert from pixels/ms to pixels/s + pixelsPerSecond: Offset( + xFit.coefficients[1] * 1000, yFit.coefficients[1] * 1000), + confidence: xFit.confidence * yFit.confidence, + duration: newestSample.time - oldestSample.time, + offset: newestSample.point - oldestSample.point, + ); + } + } + } + + // We're unable to make a velocity estimate but we did have at least one + // valid pointer position. + return VelocityEstimate( + pixelsPerSecond: Offset.zero, + confidence: 1.0, + duration: newestSample.time - oldestSample.time, + offset: newestSample.point - oldestSample.point, + ); + } + + /// Computes the velocity of the pointer at the time of the last + /// provided data point. + /// + /// This can be expensive. Only call this when you need the velocity. + /// + /// Returns [Velocity.zero] if there is no data from which to compute an + /// estimate or if the estimated velocity is zero. + @override + Velocity getVelocity() { + final VelocityEstimate? estimate = getVelocityEstimate(); + if (estimate == null || estimate.pixelsPerSecond == Offset.zero) { + return Velocity.zero; + } + return Velocity(pixelsPerSecond: estimate.pixelsPerSecond); + } +} diff --git a/lib/src/gesture_detector/velocity_tracker.dart b/lib/src/gesture_detector/velocity_tracker.dart index 3c83598b..3418f048 100644 --- a/lib/src/gesture_detector/velocity_tracker.dart +++ b/lib/src/gesture_detector/velocity_tracker.dart @@ -1,146 +1,27 @@ -// ignore_for_file: unnecessary_null_comparison - -import 'package:flutter/gestures.dart'; -part 'velocity_tracker_mixin.dart'; - -class _PointAtTime { - const _PointAtTime(this.point, this.time) - : assert(point != null), - assert(time != null); - - final Duration time; - final Offset point; - - @override - String toString() => '_PointAtTime($point at $time)'; -} - -class ExtendedVelocityTracker extends VelocityTracker - with VelocityTrackerMixin { - /// Create a new velocity tracker for a pointer [kind]. - ExtendedVelocityTracker.withKind(PointerDeviceKind kind) - : super.withKind(kind); - - static const int _assumePointerMoveStoppedMilliseconds = 40; - static const int _historySize = 20; - static const int _horizonMilliseconds = 100; - static const int _minSampleSize = 3; - - // /// The kind of pointer this tracker is for. - // @override - // final PointerDeviceKind kind; - - // Circular buffer; current sample at _index. - @override - final List<_PointAtTime?> _samples = - List<_PointAtTime?>.filled(_historySize, null); - int _index = 0; - - /// Adds a position as the given time to the tracker. - @override - void addPosition(Duration time, Offset position) { - _index += 1; - if (_index == _historySize) { - _index = 0; - } - _samples[_index] = _PointAtTime(position, time); - } - - /// Returns an estimate of the velocity of the object being tracked by the - /// tracker given the current information available to the tracker. - /// - /// Information is added using [addPosition]. - /// - /// Returns null if there is no data on which to base an estimate. - @override - VelocityEstimate? getVelocityEstimate() { - final List x = []; - final List y = []; - final List w = []; - final List time = []; - int sampleCount = 0; - int index = _index; - - final _PointAtTime? newestSample = _samples[index]; - if (newestSample == null) { - return null; - } - - _PointAtTime previousSample = newestSample; - _PointAtTime oldestSample = newestSample; - - // Starting with the most recent PointAtTime sample, iterate backwards while - // the samples represent continuous motion. - do { - final _PointAtTime? sample = _samples[index]; - if (sample == null) { +part of 'official.dart'; + +class ExtendedVelocityTracker extends _VelocityTracker { + ExtendedVelocityTracker.withKind(super.kind) : super.withKind(); + Offset getSamplesDelta() { + Offset? first; + Offset? last; + for (int i = 0; i < _samples.length; i++) { + final _PointAtTime? d = _samples[i]; + if (d != null && first == null) { + first = d.point; break; } + } - final double age = - (newestSample.time - sample.time).inMicroseconds.toDouble() / 1000; - final double delta = - (sample.time - previousSample.time).inMicroseconds.abs().toDouble() / - 1000; - previousSample = sample; - if (age > _horizonMilliseconds || - delta > _assumePointerMoveStoppedMilliseconds) { + for (int i = _samples.length - 1; i >= 0; i--) { + final _PointAtTime? d = _samples[i]; + if (d != null && last == null) { + last = d.point; break; } - - oldestSample = sample; - final Offset position = sample.point; - x.add(position.dx); - y.add(position.dy); - w.add(1.0); - time.add(-age); - index = (index == 0 ? _historySize : index) - 1; - - sampleCount += 1; - } while (sampleCount < _historySize); - - if (sampleCount >= _minSampleSize) { - final LeastSquaresSolver xSolver = LeastSquaresSolver(time, x, w); - final PolynomialFit? xFit = xSolver.solve(2); - if (xFit != null) { - final LeastSquaresSolver ySolver = LeastSquaresSolver(time, y, w); - final PolynomialFit? yFit = ySolver.solve(2); - if (yFit != null) { - return VelocityEstimate( - // convert from pixels/ms to pixels/s - pixelsPerSecond: Offset( - xFit.coefficients[1] * 1000, yFit.coefficients[1] * 1000), - confidence: xFit.confidence * yFit.confidence, - duration: newestSample.time - oldestSample.time, - offset: newestSample.point - oldestSample.point, - ); - } - } - } - - // We're unable to make a velocity estimate but we did have at least one - // valid pointer position. - return VelocityEstimate( - pixelsPerSecond: Offset.zero, - confidence: 1.0, - duration: newestSample.time - oldestSample.time, - offset: newestSample.point - oldestSample.point, - ); - } - - /// Computes the velocity of the pointer at the time of the last - /// provided data point. - /// - /// This can be expensive. Only call this when you need the velocity. - /// - /// Returns [Velocity.zero] if there is no data from which to compute an - /// estimate or if the estimated velocity is zero. - @override - Velocity getVelocity() { - final VelocityEstimate? estimate = getVelocityEstimate(); - if (estimate == null || estimate.pixelsPerSecond == Offset.zero) { - return Velocity.zero; } - return Velocity(pixelsPerSecond: estimate.pixelsPerSecond); + last ??= Offset.zero; + first ??= Offset.zero; + return last - first; } } diff --git a/lib/src/gesture_detector/velocity_tracker_mixin.dart b/lib/src/gesture_detector/velocity_tracker_mixin.dart deleted file mode 100644 index 15006a2d..00000000 --- a/lib/src/gesture_detector/velocity_tracker_mixin.dart +++ /dev/null @@ -1,27 +0,0 @@ -part of 'velocity_tracker.dart'; - -mixin VelocityTrackerMixin on VelocityTracker { - List<_PointAtTime?> get _samples; - Offset getSamplesDelta() { - Offset? first; - Offset? last; - for (int i = 0; i < _samples.length; i++) { - final _PointAtTime? d = _samples[i]; - if (d != null && first == null) { - first = d.point; - break; - } - } - - for (int i = _samples.length - 1; i >= 0; i--) { - final _PointAtTime? d = _samples[i]; - if (d != null && last == null) { - last = d.point; - break; - } - } - last ??= Offset.zero; - first ??= Offset.zero; - return last - first; - } -} diff --git a/lib/src/image/painting.dart b/lib/src/image/painting.dart index c9b4b437..aa5c53c0 100644 --- a/lib/src/image/painting.dart +++ b/lib/src/image/painting.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_visible_for_testing_member + import 'dart:math'; import 'dart:ui' as ui show Image; import 'package:extended_image/extended_image.dart'; @@ -180,7 +182,8 @@ void paintExtendedImage( if (needSave) { canvas.save(); } - if (repeat != ImageRepeat.noRepeat) { + if (repeat != ImageRepeat.noRepeat && centerSlice != null) { + // Don't clip if an image shader is used. canvas.clipRect(paintRect); } if (flipHorizontally) { @@ -196,9 +199,12 @@ void paintExtendedImage( if (repeat == ImageRepeat.noRepeat) { canvas.drawImageRect(image, sourceRect, destinationRect, paint); } else { - for (final Rect tileRect - in _generateImageTileRects(rect, destinationRect, repeat)) - canvas.drawImageRect(image, sourceRect, tileRect, paint); + final ImageTilingInfo info = + createTilingInfo(repeat, rect, destinationRect, sourceRect); + final ImageShader shader = ImageShader( + image, info.tmx, info.tmy, info.transform.storage, + filterQuality: filterQuality); + canvas.drawRect(rect, paint..shader = shader); } } else { canvas.scale(1 / scale); @@ -226,8 +232,8 @@ void paintExtendedImage( } } -Iterable _generateImageTileRects( - Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) sync* { +List _generateImageTileRects( + Rect outputRect, Rect fundamentalRect, ImageRepeat repeat) { int startX = 0; int startY = 0; int stopX = 0; @@ -245,11 +251,11 @@ Iterable _generateImageTileRects( stopY = ((outputRect.bottom - fundamentalRect.bottom) / strideY).ceil(); } - for (int i = startX; i <= stopX; ++i) { - for (int j = startY; j <= stopY; ++j) { - yield fundamentalRect.shift(Offset(i * strideX, j * strideY)); - } - } + return [ + for (int i = startX; i <= stopX; ++i) + for (int j = startY; j <= stopY; ++j) + fundamentalRect.shift(Offset(i * strideX, j * strideY)), + ]; } Rect _scaleRect(Rect rect, double scale) => Rect.fromLTRB(rect.left * scale, diff --git a/lib/src/utils.dart b/lib/src/utils.dart index 3de26b10..6c2c2faa 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -13,7 +13,7 @@ enum LoadState { failed } -abstract class ExtendedImageState { +mixin ExtendedImageState { void reLoadImage(); ImageInfo? get extendedImageInfo; LoadState get extendedImageLoadState; diff --git a/pubspec.yaml b/pubspec.yaml index 9c8e7d94..1a8b6780 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: extended_image description: Official extension image, support placeholder(loading)/ failed state, cache network, zoom/pan, photo view, slide out page, editor(crop,rotate,flip), painting etc. -version: 7.0.2 +version: 8.0.1 homepage: https://github.com/fluttercandies/extended_image environment: - sdk: '>=2.18.0 <3.0.0' - flutter: '>=3.7.0' + sdk: '>=2.18.0 <4.0.0' + flutter: '>=3.10.0' dependencies: extended_image_library: ^3.4.0