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"> - - - - + + + + + + - - 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 00000000..eb9b4d76 Binary files /dev/null and b/example/web/icons/Icon-maskable-192.png differ diff --git a/example/web/icons/Icon-maskable-512.png b/example/web/icons/Icon-maskable-512.png new file mode 100644 index 00000000..d69c5669 Binary files /dev/null and b/example/web/icons/Icon-maskable-512.png differ 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