├── .pubignore
├── example
├── ios
│ ├── Flutter
│ │ ├── Debug.xcconfig
│ │ ├── Release.xcconfig
│ │ └── AppFrameworkInfo.plist
│ ├── Runner
│ │ ├── Runner-Bridging-Header.h
│ │ ├── Assets.xcassets
│ │ │ ├── LaunchImage.imageset
│ │ │ │ ├── LaunchImage.png
│ │ │ │ ├── LaunchImage@2x.png
│ │ │ │ ├── LaunchImage@3x.png
│ │ │ │ ├── README.md
│ │ │ │ └── Contents.json
│ │ │ └── AppIcon.appiconset
│ │ │ │ ├── Icon-App-20x20@1x.png
│ │ │ │ ├── Icon-App-20x20@2x.png
│ │ │ │ ├── Icon-App-20x20@3x.png
│ │ │ │ ├── Icon-App-29x29@1x.png
│ │ │ │ ├── Icon-App-29x29@2x.png
│ │ │ │ ├── Icon-App-29x29@3x.png
│ │ │ │ ├── Icon-App-40x40@1x.png
│ │ │ │ ├── Icon-App-40x40@2x.png
│ │ │ │ ├── Icon-App-40x40@3x.png
│ │ │ │ ├── Icon-App-60x60@2x.png
│ │ │ │ ├── Icon-App-60x60@3x.png
│ │ │ │ ├── Icon-App-76x76@1x.png
│ │ │ │ ├── Icon-App-76x76@2x.png
│ │ │ │ ├── Icon-App-1024x1024@1x.png
│ │ │ │ ├── Icon-App-83.5x83.5@2x.png
│ │ │ │ └── Contents.json
│ │ ├── AppDelegate.swift
│ │ ├── Base.lproj
│ │ │ ├── Main.storyboard
│ │ │ └── LaunchScreen.storyboard
│ │ └── Info.plist
│ ├── Runner.xcodeproj
│ │ ├── project.xcworkspace
│ │ │ ├── contents.xcworkspacedata
│ │ │ └── xcshareddata
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── IDEWorkspaceChecks.plist
│ │ ├── xcshareddata
│ │ │ └── xcschemes
│ │ │ │ └── Runner.xcscheme
│ │ └── project.pbxproj
│ ├── Runner.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ └── IDEWorkspaceChecks.plist
│ └── .gitignore
├── images
│ └── sample.jpg
├── web
│ ├── favicon.png
│ ├── icons
│ │ ├── Icon-192.png
│ │ ├── Icon-512.png
│ │ ├── Icon-maskable-192.png
│ │ └── Icon-maskable-512.png
│ ├── manifest.json
│ └── index.html
├── android
│ ├── gradle.properties
│ ├── app
│ │ ├── src
│ │ │ ├── main
│ │ │ │ ├── res
│ │ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ │ └── ic_launcher.png
│ │ │ │ │ ├── drawable
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── drawable-v21
│ │ │ │ │ │ └── launch_background.xml
│ │ │ │ │ ├── values
│ │ │ │ │ │ └── styles.xml
│ │ │ │ │ └── values-night
│ │ │ │ │ │ └── styles.xml
│ │ │ │ ├── kotlin
│ │ │ │ │ └── dev
│ │ │ │ │ │ └── pub
│ │ │ │ │ │ └── crop
│ │ │ │ │ │ └── app
│ │ │ │ │ │ └── MainActivity.kt
│ │ │ │ └── AndroidManifest.xml
│ │ │ ├── debug
│ │ │ │ └── AndroidManifest.xml
│ │ │ └── profile
│ │ │ │ └── AndroidManifest.xml
│ │ └── build.gradle
│ ├── gradle
│ │ └── wrapper
│ │ │ └── gradle-wrapper.properties
│ ├── .gitignore
│ ├── settings.gradle
│ └── build.gradle
├── analysis_options.yaml
├── .gitignore
├── test
│ └── widget_test.dart
├── .metadata
├── pubspec.yaml
├── README.md
└── lib
│ ├── centered_slider_track_shape.dart
│ └── main.dart
├── .metadata
├── .github
└── workflows
│ ├── publish_pubdev.yml
│ └── github-pages.yaml
├── test
└── crop_test.dart
├── lib
├── src
│ ├── matrix_decomposition.dart
│ ├── utils.dart
│ ├── crop_render_object_widget.dart
│ ├── crop_controller.dart
│ ├── crop_render.dart
│ └── crop.dart
└── crop.dart
├── pubspec.yaml
├── LICENSE
├── README.md
├── .gitignore
├── CHANGELOG.md
└── analysis_options.yaml
/.pubignore:
--------------------------------------------------------------------------------
1 | docs/
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Debug.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Flutter/Release.xcconfig:
--------------------------------------------------------------------------------
1 | #include "Generated.xcconfig"
2 |
--------------------------------------------------------------------------------
/example/ios/Runner/Runner-Bridging-Header.h:
--------------------------------------------------------------------------------
1 | #import "GeneratedPluginRegistrant.h"
2 |
--------------------------------------------------------------------------------
/example/images/sample.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/images/sample.jpg
--------------------------------------------------------------------------------
/example/web/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/favicon.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-512.png
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | org.gradle.jvmargs=-Xmx1536M
2 | android.useAndroidX=true
3 | android.enableJetifier=true
4 |
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-maskable-192.png
--------------------------------------------------------------------------------
/example/web/icons/Icon-maskable-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/web/icons/Icon-maskable-512.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png
--------------------------------------------------------------------------------
/example/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 |
3 | # Additional information about this file can be found at
4 | # https://dart.dev/guides/language/analysis-options
5 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/olutter/crop/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png
--------------------------------------------------------------------------------
/example/android/app/src/main/kotlin/dev/pub/crop/app/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package dev.pub.crop.app
2 |
3 | import io.flutter.embedding.android.FlutterActivity
4 |
5 | class MainActivity: FlutterActivity() {
6 | }
7 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/example/android/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | zipStoreBase=GRADLE_USER_HOME
4 | zipStorePath=wrapper/dists
5 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip
6 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled and should not be manually edited.
5 |
6 | version:
7 | revision: 5d37de2685b8fcc151f61e121289c3dcbaee4623
8 | channel: master
9 |
10 | project_type: package
11 |
--------------------------------------------------------------------------------
/example/android/.gitignore:
--------------------------------------------------------------------------------
1 | gradle-wrapper.jar
2 | /.gradle
3 | /captures/
4 | /gradlew
5 | /gradlew.bat
6 | /local.properties
7 | GeneratedPluginRegistrant.java
8 |
9 | # Remember to never publicly share your keystore.
10 | # See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
11 | key.properties
12 | **/*.keystore
13 | **/*.jks
14 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md:
--------------------------------------------------------------------------------
1 | # Launch Screen Assets
2 |
3 | You can customize the launch screen with your own desired assets by replacing the image files in this directory.
4 |
5 | You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images.
--------------------------------------------------------------------------------
/.github/workflows/publish_pubdev.yml:
--------------------------------------------------------------------------------
1 | name: Publish to Pub.dev
2 |
3 | on: push
4 |
5 | jobs:
6 | publishing:
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: 'Checkout'
10 | uses: actions/checkout@v2 # required!
11 |
12 | - name: 'crop'
13 | uses: k-paxian/dart-package-publisher@master
14 | with:
15 | credentialJson: ${{ secrets.CREDENTIAL_JSON }}
16 |
--------------------------------------------------------------------------------
/test/crop_test.dart:
--------------------------------------------------------------------------------
1 | import 'package:flutter_test/flutter_test.dart';
2 |
3 | //import 'package:crop/crop.dart';
4 |
5 | void main() {
6 | test('No tests yet.', () {
7 | // final calculator = Calculator();
8 | // expect(calculator.addOne(2), 3);
9 | // expect(calculator.addOne(-7), -6);
10 | // expect(calculator.addOne(0), 1);
11 | // expect(() => calculator.addOne(null), throwsNoSuchMethodError);
12 | });
13 | }
14 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/android/app/src/profile/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/example/ios/Runner/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import Flutter
3 |
4 | @UIApplicationMain
5 | @objc class AppDelegate: FlutterAppDelegate {
6 | override func application(
7 | _ application: UIApplication,
8 | didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
9 | ) -> Bool {
10 | GeneratedPluginRegistrant.register(with: self)
11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/lib/src/matrix_decomposition.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui';
2 |
3 | /// Decomposition of a matrix into [rotation], [scale], [translation].
4 | class MatrixDecomposition {
5 | /// Construction
6 | MatrixDecomposition({
7 | required this.scale,
8 | required this.rotation,
9 | required this.translation,
10 | });
11 |
12 | /// Rotation
13 | final double rotation;
14 |
15 | /// Scale
16 | final double scale;
17 |
18 | /// Translation
19 | final Offset translation;
20 | }
21 |
--------------------------------------------------------------------------------
/example/android/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
3 | def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
4 | def properties = new Properties()
5 |
6 | assert localPropertiesFile.exists()
7 | localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
8 |
9 | def flutterSdkPath = properties.getProperty("flutter.sdk")
10 | assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
11 | apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
12 |
--------------------------------------------------------------------------------
/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: crop
2 | description: Crop for Flutter. Crop any widget/image in Android, iOS, Web and Desktop with fancy and customizable UI, in 100% pure Dart code.
3 | version: 0.5.5
4 | homepage: https://pwa.ir
5 | repository: https://github.com/xclud/flutter_crop
6 |
7 | environment:
8 | sdk: ">=2.12.0 <3.0.0"
9 |
10 | dependencies:
11 | flutter:
12 | sdk: flutter
13 | collision: ^0.0.3
14 | vector_math: ^2.1.2
15 |
16 | dev_dependencies:
17 | flutter_test:
18 | sdk: flutter
19 | flutter_lints: ^2.0.1
20 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable-v21/launch_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
12 |
13 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "universal",
5 | "filename" : "LaunchImage.png",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "filename" : "LaunchImage@2x.png",
11 | "scale" : "2x"
12 | },
13 | {
14 | "idiom" : "universal",
15 | "filename" : "LaunchImage@3x.png",
16 | "scale" : "3x"
17 | }
18 | ],
19 | "info" : {
20 | "version" : 1,
21 | "author" : "xcode"
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/lib/crop.dart:
--------------------------------------------------------------------------------
1 | /// Crop for Flutter. Crop any widget/image in Android, iOS, Web and Desktop with fancy and customizable UI, in 100% pure Dart code.
2 | library crop;
3 |
4 | import 'dart:ui' as ui;
5 | import 'dart:math';
6 |
7 | import 'package:collision/collision.dart';
8 | import 'package:crop/src/matrix_decomposition.dart';
9 | import 'package:flutter/material.dart';
10 | import 'package:flutter/rendering.dart';
11 | import 'package:vector_math/vector_math_64.dart' as vm;
12 |
13 | export 'src/matrix_decomposition.dart';
14 |
15 | part 'src/crop.dart';
16 | part 'src/crop_render.dart';
17 | part 'src/crop_controller.dart';
18 | part 'src/utils.dart';
19 | part 'src/crop_render_object_widget.dart';
20 |
--------------------------------------------------------------------------------
/example/ios/.gitignore:
--------------------------------------------------------------------------------
1 | **/dgph
2 | *.mode1v3
3 | *.mode2v3
4 | *.moved-aside
5 | *.pbxuser
6 | *.perspectivev3
7 | **/*sync/
8 | .sconsign.dblite
9 | .tags*
10 | **/.vagrant/
11 | **/DerivedData/
12 | Icon?
13 | **/Pods/
14 | **/.symlinks/
15 | profile
16 | xcuserdata
17 | **/.generated/
18 | Flutter/App.framework
19 | Flutter/Flutter.framework
20 | Flutter/Flutter.podspec
21 | Flutter/Generated.xcconfig
22 | Flutter/ephemeral/
23 | Flutter/app.flx
24 | Flutter/app.zip
25 | Flutter/flutter_assets/
26 | Flutter/flutter_export_environment.sh
27 | ServiceDefinitions.json
28 | Runner/GeneratedPluginRegistrant.*
29 |
30 | # Exceptions to above rules.
31 | !default.mode1v3
32 | !default.mode2v3
33 | !default.pbxuser
34 | !default.perspectivev3
35 |
--------------------------------------------------------------------------------
/example/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | /build/
32 |
33 | # Web related
34 |
35 | # Exceptions to above rules.
36 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
37 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | ext.kotlin_version = '1.7.10'
3 | repositories {
4 | google()
5 | mavenCentral()
6 | }
7 |
8 | dependencies {
9 | classpath 'com.android.tools.build:gradle:7.2.0'
10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | google()
17 | mavenCentral()
18 | }
19 | }
20 |
21 | rootProject.buildDir = '../build'
22 | subprojects {
23 | project.buildDir = "${rootProject.buildDir}/${project.name}"
24 | }
25 | subprojects {
26 | project.evaluationDependsOn(':app')
27 | }
28 |
29 | task clean(type: Delete) {
30 | delete rootProject.buildDir
31 | }
32 |
--------------------------------------------------------------------------------
/example/ios/Flutter/AppFrameworkInfo.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | App
9 | CFBundleIdentifier
10 | io.flutter.flutter.app
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | App
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1.0
23 | MinimumOSVersion
24 | 11.0
25 |
26 |
27 |
--------------------------------------------------------------------------------
/lib/src/utils.dart:
--------------------------------------------------------------------------------
1 | part of crop;
2 |
3 | Size _getSizeToFitByRatio(double imageAspectRatio, double containerWidth,
4 | double containerHeight, EdgeInsets padding) {
5 | var targetAspectRatio = containerWidth / containerHeight;
6 |
7 | // no need to adjust the size if current size is square
8 | var adjustedWidth = containerWidth;
9 | var adjustedHeight = containerHeight;
10 |
11 | // get the larger aspect ratio of the two
12 | // if aspect ratio is 1 then no adjustment needed
13 | if (imageAspectRatio > targetAspectRatio) {
14 | adjustedHeight = containerWidth / imageAspectRatio;
15 | } else if (imageAspectRatio < targetAspectRatio) {
16 | adjustedWidth = containerHeight * imageAspectRatio;
17 | }
18 |
19 | // set the adjusted size (same if square)
20 | return Size(
21 | adjustedWidth - padding.horizontal,
22 | adjustedHeight - padding.vertical,
23 | );
24 | }
25 |
26 | vm.Vector2 _toVector2(Offset offset) => vm.Vector2(offset.dx, offset.dy);
27 | Offset _toOffset(vm.Vector2 v) => Offset(v.x, v.y);
28 |
--------------------------------------------------------------------------------
/example/web/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Crop Demo",
3 | "short_name": "Crop Demo",
4 | "start_url": ".",
5 | "display": "standalone",
6 | "background_color": "#0175C2",
7 | "theme_color": "#0175C2",
8 | "description": "A new Flutter project.",
9 | "orientation": "portrait-primary",
10 | "prefer_related_applications": false,
11 | "icons": [
12 | {
13 | "src": "icons/Icon-192.png",
14 | "sizes": "192x192",
15 | "type": "image/png"
16 | },
17 | {
18 | "src": "icons/Icon-512.png",
19 | "sizes": "512x512",
20 | "type": "image/png"
21 | },
22 | {
23 | "src": "icons/Icon-maskable-192.png",
24 | "sizes": "192x192",
25 | "type": "image/png",
26 | "purpose": "maskable"
27 | },
28 | {
29 | "src": "icons/Icon-maskable-512.png",
30 | "sizes": "512x512",
31 | "type": "image/png",
32 | "purpose": "maskable"
33 | }
34 | ]
35 | }
36 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/values-night/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
9 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020-2021 Mahdi K. Fard
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/example/test/widget_test.dart:
--------------------------------------------------------------------------------
1 | // This is a basic Flutter widget test.
2 | //
3 | // To perform an interaction with a widget in your test, use the WidgetTester
4 | // utility that Flutter provides. For example, you can send tap and scroll
5 | // gestures. You can also use WidgetTester to find child widgets in the widget
6 | // tree, read text, and verify that the values of widget properties are correct.
7 |
8 | //import 'package:flutter/material.dart';
9 | import 'package:flutter_test/flutter_test.dart';
10 |
11 | //import 'package:app/main.dart';
12 |
13 | void main() {
14 | testWidgets('Counter increments smoke test', (WidgetTester tester) async {
15 | // // Build our app and trigger a frame.
16 | // await tester.pumpWidget(const MyApp());
17 |
18 | // // Verify that our counter starts at 0.
19 | // expect(find.text('0'), findsOneWidget);
20 | // expect(find.text('1'), findsNothing);
21 |
22 | // // Tap the '+' icon and trigger a frame.
23 | // await tester.tap(find.byIcon(Icons.add));
24 | // await tester.pump();
25 |
26 | // // Verify that our counter has incremented.
27 | // expect(find.text('0'), findsNothing);
28 | // expect(find.text('1'), findsOneWidget);
29 | });
30 | }
31 |
--------------------------------------------------------------------------------
/.github/workflows/github-pages.yaml:
--------------------------------------------------------------------------------
1 | name: GitHub Pages
2 |
3 | concurrency:
4 | group: production
5 | cancel-in-progress: true
6 |
7 | on:
8 | push:
9 | branches: [ main ]
10 |
11 | jobs:
12 | build:
13 | name: Build Web App
14 | runs-on: ubuntu-latest
15 | steps:
16 | - uses: actions/checkout@v3
17 | - name: Fetch all history for all tags and branches
18 | run: |
19 | git config remote.origin.url https://x-access-token:${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}
20 | git fetch --prune --depth=10000
21 | - uses: subosito/flutter-action@v2
22 | with:
23 | channel: "master"
24 | - run: cd example && flutter pub get
25 | - run: cd example && flutter test
26 | - name: Build Web App
27 | run: cd example && flutter build web --base-href /flutter_crop/
28 | - name: Upload Artifacts
29 | uses: actions/upload-pages-artifact@v1
30 | with:
31 | path: example/build/web
32 | deploy:
33 | name: Deploy GitHub Pages
34 | needs: build
35 | runs-on: ubuntu-latest
36 | permissions:
37 | pages: write
38 | id-token: write
39 | environment:
40 | name: github-pages
41 | url: ${{ steps.deployment.outputs.page_url }}
42 | steps:
43 | - name: Deploy to GitHub Pages
44 | id: deployment
45 | uses: actions/deploy-pages@v1
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | [](https://pub.dartlang.org/packages/crop)
2 |
3 | A Flutter package for cropping any widget, not only images. This package is entirely written in Dart and supports Android, iOS, Web and Desktop. Also, because of being independent from native platform, it does not increase size of your apps output (e.g. apk).
4 |
5 | ## Supported platforms
6 |
7 | * Flutter Android
8 | * Flutter iOS
9 | * Flutter Web
10 | * Flutter Desktop
11 |
12 | ## Demo
13 |
14 | [Web Demo](https://xclud.github.io/flutter_crop/) | [Install from Google Play](https://play.google.com/store/apps/details?id=dev.pub.crop.app)
15 |
16 | ## Donation
17 |
18 | If you find this project useful, please support me by buying me a pizza 🍕.
19 |
20 | Tron Address:
21 |
22 | ```bash
23 | TLtrEU4KT2bn5J87VWfs1QDrmB1aFQ1bja
24 | ```
25 |
26 | Ethereum Address:
27 |
28 | ```bash
29 | 0xf8Da77e7BbE39be8c9e527289465Bf7219af58db
30 | ```
31 |
32 | I do not accept Bitcoin due to its issues with sustainability and global warming.
33 |
34 | ## Getting Started
35 |
36 | In your `pubspec.yaml` file add:
37 |
38 | ```dart
39 | dependencies:
40 | crop: any
41 | ```
42 |
43 | Then, in your code import:
44 |
45 | ```dart
46 | import 'package:crop/crop.dart';
47 | ```
48 |
49 | Now in build function, put a `Crop` widget in the widget tree and you are done. Please don't forget to check ```/example``` folder, there is much more.
50 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/example/.metadata:
--------------------------------------------------------------------------------
1 | # This file tracks properties of this Flutter project.
2 | # Used by Flutter tool to assess capabilities and perform upgrades etc.
3 | #
4 | # This file should be version controlled.
5 |
6 | version:
7 | revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
8 | channel: master
9 |
10 | project_type: app
11 |
12 | # Tracks metadata for the flutter migrate command
13 | migration:
14 | platforms:
15 | - platform: root
16 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
17 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
18 | - platform: android
19 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
20 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
21 | - platform: ios
22 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
23 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
24 | - platform: linux
25 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
26 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
27 | - platform: macos
28 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
29 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
30 | - platform: web
31 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
32 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
33 | - platform: windows
34 | create_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
35 | base_revision: a707d05e9d4c7ec95403444e04a65cd69c0da3b0
36 |
37 | # User provided section
38 |
39 | # List of Local paths (relative to this file) that should be
40 | # ignored by the migrate tool.
41 | #
42 | # Files that are not part of the templates will be ignored by default.
43 | unmanaged_files:
44 | - 'lib/main.dart'
45 | - 'ios/Runner.xcodeproj/project.pbxproj'
46 |
--------------------------------------------------------------------------------
/example/android/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
8 |
16 |
20 |
24 |
25 |
26 |
27 |
28 |
29 |
31 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/
17 |
18 | # The .vscode folder contains launch configuration and tasks you configure in
19 | # VS Code which you may wish to be included in version control, so this line
20 | # is commented out by default.
21 | #.vscode/
22 |
23 | # Flutter/Dart/Pub related
24 | **/doc/api/
25 | .dart_tool/
26 | .flutter-plugins
27 | .flutter-plugins-dependencies
28 | .packages
29 | .pub-cache/
30 | .pub/
31 | build/
32 |
33 | # Android related
34 | **/android/**/gradle-wrapper.jar
35 | **/android/.gradle
36 | **/android/captures/
37 | **/android/gradlew
38 | **/android/gradlew.bat
39 | **/android/local.properties
40 | **/android/**/GeneratedPluginRegistrant.java
41 |
42 | # iOS/XCode related
43 | **/ios/**/*.mode1v3
44 | **/ios/**/*.mode2v3
45 | **/ios/**/*.moved-aside
46 | **/ios/**/*.pbxuser
47 | **/ios/**/*.perspectivev3
48 | **/ios/**/*sync/
49 | **/ios/**/.sconsign.dblite
50 | **/ios/**/.tags*
51 | **/ios/**/.vagrant/
52 | **/ios/**/DerivedData/
53 | **/ios/**/Icon?
54 | **/ios/**/Pods/
55 | **/ios/**/.symlinks/
56 | **/ios/**/profile
57 | **/ios/**/xcuserdata
58 | **/ios/.generated/
59 | **/ios/Flutter/App.framework
60 | **/ios/Flutter/Flutter.framework
61 | **/ios/Flutter/Flutter.podspec
62 | **/ios/Flutter/Generated.xcconfig
63 | **/ios/Flutter/app.flx
64 | **/ios/Flutter/app.zip
65 | **/ios/Flutter/flutter_assets/
66 | **/ios/Flutter/flutter_export_environment.sh
67 | **/ios/ServiceDefinitions.json
68 | **/ios/Runner/GeneratedPluginRegistrant.*
69 |
70 | # Exceptions to above rules.
71 | !**/ios/**/default.mode1v3
72 | !**/ios/**/default.mode2v3
73 | !**/ios/**/default.pbxuser
74 | !**/ios/**/default.perspectivev3
75 | !/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages
76 | /example/pubspec.lock
77 | /pubspec.lock
78 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 0.5.5
2 |
3 | - Allow users to set their own offset.
4 |
5 | ## 0.5.4
6 |
7 | - More dartdoc comments.
8 |
9 | ## 0.5.3
10 |
11 | - Upgrade to flutter_lints version 2.
12 |
13 | ## 0.5.1
14 |
15 | - Support two-finger rotation gesture.
16 | - Documentations.
17 |
18 | ## 0.5.0
19 |
20 | - Flutter 2.0 and Dart 2.12.0 support.
21 |
22 | ## 0.4.3
23 |
24 | - Migration to null-safety
25 |
26 | ## 0.4.2
27 |
28 | - Web support (via CanvasKit/Skia)
29 |
30 | ## 0.4.1
31 |
32 | - Crop widget gets `onChanged` propery. Thanks to @sanjul.
33 |
34 | ## 0.4.0
35 |
36 | - Adds support for Oval crop area. Special thanks to @songfei for his contribution and inspiration.
37 | - Example project updated with a crop shape selector.
38 |
39 | ## 0.3.2
40 |
41 | - Fixes an issue causing the image go off-canvas when playing with aspect-ratio and panning.
42 |
43 | ## 0.3.1
44 |
45 | - Fixes a bug when the rotation is more than 90 degrees (or less than -90).
46 | - Updated the example project to let the rotation slider move between -180 and +180 degrees.
47 |
48 | ## 0.3.0
49 |
50 | - Introduces `helper` parameter.
51 | - Ditches `borderWidth` and `borderColor` parameters in favor of the new `helper` parameter (Breaking Change). You should see the example project to figure out how to bring borders back to your UI.
52 |
53 | ## 0.2.2
54 |
55 | - Adds Support for background widget.
56 | - Layout and Paint more efficiently.
57 |
58 | ## 0.2.1
59 |
60 | - Fixes a bug when child's aspect-ratio does not match the canvas's aspect ratio.
61 |
62 | ## 0.2.0
63 |
64 | - Introduced CropController (Breaking Change).
65 |
66 | ## 0.1.3
67 |
68 | - Optionally takes pixelRatio for better output quality.
69 |
70 | ## 0.1.1
71 |
72 | - Update documentation and improvements to the package health.
73 |
74 | ## 0.1.0
75 |
76 | - Initial release of the package.
77 |
78 | ## 0.0.1
79 |
80 | - Initialize package placeholder.
81 |
--------------------------------------------------------------------------------
/example/ios/Runner/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleDisplayName
8 | App
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | app
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(FLUTTER_BUILD_NAME)
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | $(FLUTTER_BUILD_NUMBER)
25 | LSRequiresIPhoneOS
26 |
27 | UILaunchStoryboardName
28 | LaunchScreen
29 | UIMainStoryboardFile
30 | Main
31 | UISupportedInterfaceOrientations
32 |
33 | UIInterfaceOrientationPortrait
34 | UIInterfaceOrientationLandscapeLeft
35 | UIInterfaceOrientationLandscapeRight
36 |
37 | UISupportedInterfaceOrientations~ipad
38 |
39 | UIInterfaceOrientationPortrait
40 | UIInterfaceOrientationPortraitUpsideDown
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UIViewControllerBasedStatusBarAppearance
45 |
46 | CADisableMinimumFrameDurationOnPhone
47 |
48 | UIApplicationSupportsIndirectInputEvents
49 |
50 |
51 |
52 |
--------------------------------------------------------------------------------
/example/web/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Crop Demo
33 |
34 |
35 |
39 |
40 |
41 |
42 |
43 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/analysis_options.yaml:
--------------------------------------------------------------------------------
1 | include: package:flutter_lints/flutter.yaml
2 | analyzer:
3 | exclude:
4 | - "lib/**/generated/**"
5 | language:
6 | strict-raw-types: false
7 | errors:
8 | always_declare_return_types: error
9 | always_specify_types: error
10 | always_use_package_imports: error
11 | annotate_overrides: error
12 | argument_type_not_assignable: error
13 | avoid_function_literals_in_foreach_calls: error
14 | avoid_renaming_method_parameters: error
15 | avoid_types_on_closure_parameters: error
16 | avoid_unnecessary_containers: error
17 | await_only_futures: error
18 | curly_braces_in_flow_control_structures: error
19 | dead_code: error
20 | duplicate_import: error
21 | file_names: error
22 | implicit_dynamic_function: ignore
23 | implicit_dynamic_parameter: error
24 | implicit_dynamic_list_literal: ignore
25 | implicit_dynamic_map_literal: ignore
26 | implicit_dynamic_method: ignore
27 | implicit_dynamic_type: ignore
28 | implicit_dynamic_variable: ignore
29 | invalid_assignment: error
30 | missing_return: error
31 | prefer_const_constructors: error
32 | prefer_const_constructors_in_immutables: error
33 | prefer_const_declarations: error
34 | prefer_collection_literals: error
35 | prefer_contains: error
36 | prefer_const_literals_to_create_immutables: error
37 | prefer_final_fields: error
38 | prefer_single_quotes: error
39 | public_member_api_docs: error
40 | sized_box_for_whitespace: error
41 | sort_constructors_first: error
42 | sort_unnamed_constructors_first: error
43 | use_function_type_syntax_for_parameters: error
44 | use_key_in_widget_constructors: error
45 | unnecessary_import: error
46 | unnecessary_string_interpolations: error
47 | unnecessary_this: error
48 | unused_import: error
49 | linter:
50 | rules:
51 | always_use_package_imports: true
52 | avoid_print: false
53 | prefer_single_quotes: true
54 | public_member_api_docs: true
55 | sort_constructors_first: true
56 | sort_unnamed_constructors_first: true
57 |
--------------------------------------------------------------------------------
/lib/src/crop_render_object_widget.dart:
--------------------------------------------------------------------------------
1 | part of crop;
2 |
3 | /// Render object widget with a [RenderCrop] inside.
4 | class CropRenderObjectWidget extends SingleChildRenderObjectWidget {
5 | /// Constructor.
6 | const CropRenderObjectWidget({
7 | required Widget child,
8 | required this.aspectRatio,
9 | required this.shape,
10 | Key? key,
11 | this.backgroundColor = Colors.black,
12 | this.dimColor = const Color.fromRGBO(0, 0, 0, 0.8),
13 | this.padding = EdgeInsets.zero,
14 | this.radius,
15 | }) : super(key: key, child: child);
16 |
17 | /// Aspect ratio.
18 | final double aspectRatio;
19 |
20 | /// Dim Color.
21 | final Color dimColor;
22 |
23 | /// Background color.
24 | final Color backgroundColor;
25 |
26 | /// Shape of crop area.
27 | final BoxShape shape;
28 |
29 | /// Padding of crop area.
30 | final EdgeInsets padding;
31 |
32 | /// Radius of crop area.
33 | final Radius? radius;
34 |
35 | @override
36 | RenderObject createRenderObject(BuildContext context) {
37 | return RenderCrop()
38 | ..aspectRatio = aspectRatio
39 | ..dimColor = dimColor
40 | ..backgroundColor = backgroundColor
41 | ..shape = shape
42 | ..padding = padding
43 | ..radius = radius;
44 | }
45 |
46 | @override
47 | void updateRenderObject(BuildContext context, RenderCrop renderObject) {
48 | bool needsPaint = false;
49 | bool needsLayout = false;
50 |
51 | if (renderObject.aspectRatio != aspectRatio) {
52 | renderObject.aspectRatio = aspectRatio;
53 | needsLayout = true;
54 | }
55 |
56 | if (renderObject.dimColor != dimColor) {
57 | renderObject.dimColor = dimColor;
58 | needsPaint = true;
59 | }
60 |
61 | if (renderObject.shape != shape) {
62 | renderObject.shape = shape;
63 | needsPaint = true;
64 | }
65 |
66 | if (renderObject.backgroundColor != backgroundColor) {
67 | renderObject.backgroundColor = backgroundColor;
68 | needsPaint = true;
69 | }
70 |
71 | if (needsLayout) {
72 | renderObject.markNeedsLayout();
73 | }
74 | if (needsPaint) {
75 | renderObject.markNeedsPaint();
76 | }
77 |
78 | super.updateRenderObject(context, renderObject);
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | def localProperties = new Properties()
2 | def localPropertiesFile = rootProject.file('local.properties')
3 | if (localPropertiesFile.exists()) {
4 | localPropertiesFile.withReader('UTF-8') { reader ->
5 | localProperties.load(reader)
6 | }
7 | }
8 |
9 | def flutterRoot = localProperties.getProperty('flutter.sdk')
10 | if (flutterRoot == null) {
11 | throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
12 | }
13 |
14 | def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
15 | if (flutterVersionCode == null) {
16 | flutterVersionCode = '1'
17 | }
18 |
19 | def flutterVersionName = localProperties.getProperty('flutter.versionName')
20 | if (flutterVersionName == null) {
21 | flutterVersionName = '1.0'
22 | }
23 |
24 | apply plugin: 'com.android.application'
25 | apply plugin: 'kotlin-android'
26 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
27 |
28 | android {
29 | compileSdkVersion flutter.compileSdkVersion
30 | ndkVersion flutter.ndkVersion
31 |
32 | compileOptions {
33 | sourceCompatibility JavaVersion.VERSION_1_8
34 | targetCompatibility JavaVersion.VERSION_1_8
35 | }
36 |
37 | kotlinOptions {
38 | jvmTarget = '1.8'
39 | }
40 |
41 | sourceSets {
42 | main.java.srcDirs += 'src/main/kotlin'
43 | }
44 |
45 | defaultConfig {
46 | // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
47 | applicationId "dev.pub.crop.app"
48 | // You can update the following values to match your application needs.
49 | // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
50 | minSdkVersion flutter.minSdkVersion
51 | targetSdkVersion flutter.targetSdkVersion
52 | versionCode flutterVersionCode.toInteger()
53 | versionName flutterVersionName
54 | }
55 |
56 | buildTypes {
57 | release {
58 | // TODO: Add your own signing config for the release build.
59 | // Signing with the debug keys for now, so `flutter run --release` works.
60 | signingConfig signingConfigs.debug
61 | }
62 | }
63 | }
64 |
65 | flutter {
66 | source '../..'
67 | }
68 |
69 | dependencies {
70 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
71 | }
72 |
--------------------------------------------------------------------------------
/example/ios/Runner/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/example/pubspec.yaml:
--------------------------------------------------------------------------------
1 | name: app
2 | description: Example project for Crop package.
3 | publish_to: none
4 |
5 | # The following defines the version and build number for your application.
6 | # A version number is three numbers separated by dots, like 1.2.43
7 | # followed by an optional build number separated by a +.
8 | # Both the version and the builder number may be overridden in flutter
9 | # build by specifying --build-name and --build-number, respectively.
10 | # In Android, build-name is used as versionName while build-number used as versionCode.
11 | # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
12 | # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
13 | # Read more about iOS versioning at
14 | # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
15 | version: 1.0.0+1
16 |
17 | environment:
18 | sdk: ">=2.12.0 <3.0.0"
19 |
20 | dependencies:
21 | flutter:
22 | sdk: flutter
23 | crop:
24 | path: ../
25 | url_launcher: any
26 | image_gallery_saver: any
27 | permission_handler: any
28 |
29 | dev_dependencies:
30 | flutter_test:
31 | sdk: flutter
32 | flutter_lints: ^2.0.0
33 |
34 |
35 | # For information on the generic Dart part of this file, see the
36 | # following page: https://dart.dev/tools/pub/pubspec
37 |
38 | # The following section is specific to Flutter.
39 | flutter:
40 |
41 | # The following line ensures that the Material Icons font is
42 | # included with your application, so that you can use the icons in
43 | # the material Icons class.
44 | uses-material-design: true
45 |
46 | # To add assets to your application, add an assets section, like this:
47 | assets:
48 | - images/sample.jpg
49 | # - images/a_dot_ham.jpeg
50 |
51 | # An image asset can refer to one or more resolution-specific "variants", see
52 | # https://flutter.dev/assets-and-images/#resolution-aware.
53 |
54 | # For details regarding adding assets from package dependencies, see
55 | # https://flutter.dev/assets-and-images/#from-packages
56 |
57 | # To add custom fonts to your application, add a fonts section here,
58 | # in this "flutter" section. Each entry in this list should have a
59 | # "family" key with the font family name, and a "fonts" key with a
60 | # list giving the asset and other descriptors for the font. For
61 | # example:
62 | # fonts:
63 | # - family: Schyler
64 | # fonts:
65 | # - asset: fonts/Schyler-Regular.ttf
66 | # - asset: fonts/Schyler-Italic.ttf
67 | # style: italic
68 | # - family: Trajan Pro
69 | # fonts:
70 | # - asset: fonts/TrajanPro.ttf
71 | # - asset: fonts/TrajanPro_Bold.ttf
72 | # weight: 700
73 | #
74 | # For details regarding fonts from package dependencies,
75 | # see https://flutter.dev/custom-fonts/#from-packages
76 |
--------------------------------------------------------------------------------
/lib/src/crop_controller.dart:
--------------------------------------------------------------------------------
1 | part of crop;
2 |
3 | /// The controller used to control the rotation, scale and actual cropping.
4 | class CropController extends ChangeNotifier {
5 | /// Constructor
6 | CropController({
7 | double aspectRatio = 1.0,
8 | double scale = 1.0,
9 | double rotation = 0,
10 | Offset offset = Offset.zero
11 | }) {
12 | _aspectRatio = aspectRatio;
13 | _scale = scale;
14 | _rotation = rotation;
15 | _offset = offset;
16 | }
17 | double _aspectRatio = 1;
18 | double _rotation = 0;
19 | double _scale = 1;
20 | Offset _offset = Offset.zero;
21 | Future Function(double pixelRatio)? _cropCallback;
22 |
23 | /// Gets the current aspect ratio.
24 | double get aspectRatio => _aspectRatio;
25 |
26 | /// Sets the desired aspect ratio.
27 | set aspectRatio(double value) {
28 | _aspectRatio = value;
29 | notifyListeners();
30 | }
31 |
32 | /// Gets the current scale.
33 | double get scale => max(_scale, 1);
34 |
35 | /// Sets the desired scale.
36 | set scale(double value) {
37 | _scale = max(value, 1);
38 | notifyListeners();
39 | }
40 |
41 | /// Gets the current rotation.
42 | double get rotation => _rotation;
43 |
44 | /// Sets the desired rotation.
45 | set rotation(double value) {
46 | _rotation = value;
47 | notifyListeners();
48 | }
49 |
50 | /// Gets the current offset.
51 | Offset get offset => _offset;
52 |
53 | /// Sets the desired offset.
54 | set offset(Offset value) {
55 | _offset = value;
56 | notifyListeners();
57 | }
58 |
59 | /// Gets the transformation matrix.
60 | Matrix4 get transform => Matrix4.identity()
61 | ..translate(_offset.dx, _offset.dy, 0)
62 | ..rotateZ(_rotation)
63 | ..scale(_scale, _scale, 1);
64 |
65 | double _getMinScale() {
66 | final r = vm.radians(_rotation % 360);
67 | final rabs = r.abs();
68 |
69 | final sinr = sin(rabs).abs();
70 | final cosr = cos(rabs).abs();
71 |
72 | final x = cosr * _aspectRatio + sinr;
73 | final y = sinr * _aspectRatio + cosr;
74 |
75 | final m = max(x / _aspectRatio, y);
76 |
77 | return m;
78 | }
79 |
80 | /// Capture an image of the current state of this widget and its children.
81 | ///
82 | /// The returned [ui.Image] has uncompressed raw RGBA bytes, will have
83 | /// dimensions equal to the size of the [child] widget multiplied by [pixelRatio].
84 | ///
85 | /// The [pixelRatio] describes the scale between the logical pixels and the
86 | /// size of the output image. It is independent of the
87 | /// [window.devicePixelRatio] for the device, so specifying 1.0 (the default)
88 | /// will give you a 1:1 mapping between logical pixels and the output pixels
89 | /// in the image.
90 | Future crop({double pixelRatio = 1}) {
91 | if (_cropCallback == null) {
92 | return Future.value(null);
93 | }
94 |
95 | return _cropCallback!.call(pixelRatio);
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/lib/src/crop_render.dart:
--------------------------------------------------------------------------------
1 | part of crop;
2 |
3 | /// RenderBox for [Crop].
4 | class RenderCrop extends RenderBox with RenderObjectWithChildMixin {
5 | /// Aspect ratio of the crop area.
6 | double? aspectRatio;
7 |
8 | /// Dim color of the crop area.
9 | Color? dimColor;
10 |
11 | /// Background color of the crop area.
12 | Color? backgroundColor;
13 |
14 | /// Shape of the crop area.
15 | BoxShape? shape;
16 |
17 | /// Padding of the crop area.
18 | EdgeInsets? padding;
19 |
20 | /// Radius of the crop area.
21 | Radius? radius;
22 |
23 | @override
24 | bool hitTestSelf(Offset position) => false;
25 |
26 | @override
27 | void performLayout() {
28 | final BoxConstraints constraints = this.constraints;
29 | size = constraints.biggest;
30 |
31 | if (child != null) {
32 | final forcedSize =
33 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!);
34 | child!.layout(BoxConstraints.tight(forcedSize), parentUsesSize: true);
35 | }
36 | }
37 |
38 | Path _getDimClipPath() {
39 | final center = Offset(
40 | size.width / 2,
41 | size.height / 2,
42 | );
43 |
44 | final forcedSize =
45 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!);
46 |
47 | final path = Path();
48 | final baseRect = Rect.fromCenter(
49 | center: center,
50 | width: forcedSize.width,
51 | height: forcedSize.height,
52 | );
53 |
54 | if (radius != null) {
55 | final rect = RRect.fromRectAndRadius(baseRect, radius!);
56 | path.addRRect(rect);
57 | } else {
58 | final rect = baseRect;
59 | if (shape == BoxShape.circle) {
60 | path.addOval(rect);
61 | } else if (shape == BoxShape.rectangle) {
62 | path.addRect(rect);
63 | }
64 | }
65 |
66 | path.addRect(Rect.fromLTWH(0.0, 0.0, size.width, size.height));
67 | path.fillType = PathFillType.evenOdd;
68 | return path;
69 | }
70 |
71 | @override
72 | void handleEvent(PointerEvent event, BoxHitTestEntry entry) {}
73 |
74 | @override
75 | void paint(PaintingContext context, Offset offset) {
76 | final bounds = offset & size;
77 |
78 | if (backgroundColor != null) {
79 | context.canvas.drawRect(bounds, Paint()..color = backgroundColor!);
80 | }
81 |
82 | final forcedSize =
83 | _getSizeToFitByRatio(aspectRatio!, size.width, size.height, padding!);
84 |
85 | if (child != null) {
86 | final Offset tmp = (size - forcedSize) as Offset;
87 | context.paintChild(child!, offset + tmp / 2);
88 |
89 | final clipPath = _getDimClipPath();
90 |
91 | context.pushClipPath(
92 | needsCompositing,
93 | offset,
94 | bounds,
95 | clipPath,
96 | (context, offset) {
97 | context.canvas.drawRect(bounds, Paint()..color = dimColor!);
98 | },
99 | );
100 | }
101 | }
102 | }
103 |
--------------------------------------------------------------------------------
/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "20x20",
5 | "idiom" : "iphone",
6 | "filename" : "Icon-App-20x20@2x.png",
7 | "scale" : "2x"
8 | },
9 | {
10 | "size" : "20x20",
11 | "idiom" : "iphone",
12 | "filename" : "Icon-App-20x20@3x.png",
13 | "scale" : "3x"
14 | },
15 | {
16 | "size" : "29x29",
17 | "idiom" : "iphone",
18 | "filename" : "Icon-App-29x29@1x.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "29x29",
23 | "idiom" : "iphone",
24 | "filename" : "Icon-App-29x29@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "29x29",
29 | "idiom" : "iphone",
30 | "filename" : "Icon-App-29x29@3x.png",
31 | "scale" : "3x"
32 | },
33 | {
34 | "size" : "40x40",
35 | "idiom" : "iphone",
36 | "filename" : "Icon-App-40x40@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "40x40",
41 | "idiom" : "iphone",
42 | "filename" : "Icon-App-40x40@3x.png",
43 | "scale" : "3x"
44 | },
45 | {
46 | "size" : "60x60",
47 | "idiom" : "iphone",
48 | "filename" : "Icon-App-60x60@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "60x60",
53 | "idiom" : "iphone",
54 | "filename" : "Icon-App-60x60@3x.png",
55 | "scale" : "3x"
56 | },
57 | {
58 | "size" : "20x20",
59 | "idiom" : "ipad",
60 | "filename" : "Icon-App-20x20@1x.png",
61 | "scale" : "1x"
62 | },
63 | {
64 | "size" : "20x20",
65 | "idiom" : "ipad",
66 | "filename" : "Icon-App-20x20@2x.png",
67 | "scale" : "2x"
68 | },
69 | {
70 | "size" : "29x29",
71 | "idiom" : "ipad",
72 | "filename" : "Icon-App-29x29@1x.png",
73 | "scale" : "1x"
74 | },
75 | {
76 | "size" : "29x29",
77 | "idiom" : "ipad",
78 | "filename" : "Icon-App-29x29@2x.png",
79 | "scale" : "2x"
80 | },
81 | {
82 | "size" : "40x40",
83 | "idiom" : "ipad",
84 | "filename" : "Icon-App-40x40@1x.png",
85 | "scale" : "1x"
86 | },
87 | {
88 | "size" : "40x40",
89 | "idiom" : "ipad",
90 | "filename" : "Icon-App-40x40@2x.png",
91 | "scale" : "2x"
92 | },
93 | {
94 | "size" : "76x76",
95 | "idiom" : "ipad",
96 | "filename" : "Icon-App-76x76@1x.png",
97 | "scale" : "1x"
98 | },
99 | {
100 | "size" : "76x76",
101 | "idiom" : "ipad",
102 | "filename" : "Icon-App-76x76@2x.png",
103 | "scale" : "2x"
104 | },
105 | {
106 | "size" : "83.5x83.5",
107 | "idiom" : "ipad",
108 | "filename" : "Icon-App-83.5x83.5@2x.png",
109 | "scale" : "2x"
110 | },
111 | {
112 | "size" : "1024x1024",
113 | "idiom" : "ios-marketing",
114 | "filename" : "Icon-App-1024x1024@1x.png",
115 | "scale" : "1x"
116 | }
117 | ],
118 | "info" : {
119 | "version" : 1,
120 | "author" : "xcode"
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
41 |
42 |
52 |
54 |
60 |
61 |
62 |
63 |
69 |
71 |
77 |
78 |
79 |
80 |
82 |
83 |
86 |
87 |
88 |
--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | # crop
2 |
3 | A Flutter package for cropping any widget, not only images. This package is entirely written in Dart and supports Android, iOS, Web and Desktop. Also, because of being independent from native platform, it does not increase size of your apps output (e.g. apk).
4 |
5 | [](https://play.google.com/store/apps/details?id=dev.pub.crop.app)
6 |
7 | 
8 |
9 | ## Getting Started
10 |
11 | In your `pubspec.yaml` file add:
12 |
13 | ```dart
14 | dependencies:
15 | crop: any
16 | ```
17 | Then, in your code import:
18 | ```dart
19 | import 'package:crop/crop.dart';
20 | ```
21 | Now in build function, put a Crop widget in the widget tree:
22 |
23 | ```dart
24 | import 'package:app/centered_slider_track_shape.dart';
25 | import 'package:flutter/material.dart';
26 | import 'package:crop/crop.dart';
27 |
28 | void main() => runApp(MyApp());
29 |
30 | class MyApp extends StatelessWidget {
31 | @override
32 | Widget build(BuildContext context) {
33 | return MaterialApp(
34 | title: 'Crop Demo',
35 | theme: ThemeData(
36 | primarySwatch: Colors.blue,
37 | ),
38 | home: MyHomePage(),
39 | );
40 | }
41 | }
42 |
43 | class MyHomePage extends StatefulWidget {
44 | @override
45 | _MyHomePageState createState() => _MyHomePageState();
46 | }
47 |
48 | class _MyHomePageState extends State {
49 | final _cropKey = GlobalKey();
50 | double _rotation = 0;
51 |
52 | void _cropImage() async {
53 | final cropped = await _cropKey.currentState.crop();
54 | Navigator.of(context).push(
55 | MaterialPageRoute(
56 | builder: (context) => Scaffold(
57 | appBar: AppBar(
58 | title: Text('Crop Result'),
59 | centerTitle: true,
60 | ),
61 | body: Center(
62 | child: RawImage(
63 | image: cropped,
64 | ),
65 | ),
66 | ),
67 | fullscreenDialog: true,
68 | ),
69 | );
70 | }
71 |
72 | @override
73 | Widget build(BuildContext context) {
74 | final theme = Theme.of(context);
75 | return Scaffold(
76 | appBar: AppBar(
77 | title: Text('Crop Demo'),
78 | centerTitle: true,
79 | actions: [
80 | IconButton(
81 | onPressed: _cropImage,
82 | tooltip: 'Crop',
83 | icon: Icon(Icons.crop),
84 | )
85 | ],
86 | ),
87 | body: Column(
88 | children: [
89 | Expanded(
90 | child: Crop(
91 | key: _cropKey,
92 | child: Image.asset('images/sample.jpg'),
93 | aspectRatio: 1000 / 667.0,
94 | ),
95 | ),
96 | Row(
97 | children: [
98 | IconButton(
99 | icon: Icon(Icons.undo),
100 | tooltip: 'Undo',
101 | onPressed: () {
102 | _cropKey.currentState.rotation = 0;
103 | _cropKey.currentState.scale = 1;
104 | _cropKey.currentState.offset = Offset.zero;
105 | setState(() {
106 | _rotation = 0;
107 | });
108 | },
109 | ),
110 | Expanded(
111 | child: SliderTheme(
112 | data: theme.sliderTheme.copyWith(
113 | trackShape: CenteredRectangularSliderTrackShape(),
114 | ),
115 | child: Slider(
116 | divisions: 91,
117 | value: _rotation,
118 | min: -45,
119 | max: 45,
120 | label: '$_rotation°',
121 | onChanged: (n) {
122 | setState(() {
123 | _rotation = n.roundToDouble();
124 | _cropKey.currentState.rotation = _rotation;
125 | });
126 | },
127 | ),
128 | ),
129 | ),
130 | IconButton(
131 | icon: Icon(Icons.aspect_ratio),
132 | tooltip: 'Aspect Ratio',
133 | onPressed: () {},
134 | ),
135 | ],
136 | ),
137 | ],
138 | ),
139 | );
140 | }
141 | }
142 | ```
143 |
144 | Please don't forget to check ```/example``` folder, there is much more.
145 |
--------------------------------------------------------------------------------
/example/lib/centered_slider_track_shape.dart:
--------------------------------------------------------------------------------
1 | import 'dart:math';
2 |
3 | import 'package:flutter/material.dart';
4 |
5 | class CenteredRectangularSliderTrackShape extends RectangularSliderTrackShape {
6 | @override
7 | void paint(
8 | PaintingContext context,
9 | Offset offset, {
10 | required RenderBox parentBox,
11 | required SliderThemeData sliderTheme,
12 | required Animation enableAnimation,
13 | required TextDirection textDirection,
14 | required Offset thumbCenter,
15 | Offset? secondaryOffset,
16 | bool isDiscrete = false,
17 | bool isEnabled = false,
18 | }) {
19 | // If the slider track height is less than or equal to 0, then it makes no
20 | // difference whether the track is painted or not, therefore the painting
21 | // can be a no-op.
22 | if (sliderTheme.trackHeight! <= 0) {
23 | return;
24 | }
25 |
26 | // Assign the track segment paints, which are left: active, right: inactive,
27 | // but reversed for right to left text.
28 | final ColorTween activeTrackColorTween = ColorTween(
29 | begin: sliderTheme.disabledActiveTrackColor,
30 | end: sliderTheme.activeTrackColor);
31 | final ColorTween inactiveTrackColorTween = ColorTween(
32 | begin: sliderTheme.disabledInactiveTrackColor,
33 | end: sliderTheme.inactiveTrackColor);
34 | final Paint activePaint = Paint()
35 | ..color = activeTrackColorTween.evaluate(enableAnimation)!;
36 | final Paint inactivePaint = Paint()
37 | ..color = inactiveTrackColorTween.evaluate(enableAnimation)!;
38 |
39 | final Rect trackRect = getPreferredRect(
40 | parentBox: parentBox,
41 | offset: offset,
42 | sliderTheme: sliderTheme,
43 | isEnabled: isEnabled,
44 | isDiscrete: isDiscrete,
45 | );
46 | final trackCenter = trackRect.center;
47 | final Size thumbSize =
48 | sliderTheme.thumbShape!.getPreferredSize(isEnabled, isDiscrete);
49 | // final Rect leftTrackSegment = Rect.fromLTRB(
50 | // trackRect.left + trackRect.height / 2,
51 | // trackRect.top,
52 | // thumbCenter.dx - thumbSize.width / 2,
53 | // trackRect.bottom);
54 | // if (!leftTrackSegment.isEmpty)
55 | // context.canvas.drawRect(leftTrackSegment, leftTrackPaint);
56 | // final Rect rightTrackSegment = Rect.fromLTRB(
57 | // thumbCenter.dx + thumbSize.width / 2,
58 | // trackRect.top,
59 | // trackRect.right,
60 | // trackRect.bottom);
61 | // if (!rightTrackSegment.isEmpty)
62 | // context.canvas.drawRect(rightTrackSegment, rightTrackPaint);
63 |
64 | if (trackCenter.dx < thumbCenter.dx) {
65 | final Rect leftTrackSegment = Rect.fromLTRB(
66 | trackRect.left,
67 | trackRect.top,
68 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2),
69 | trackRect.bottom);
70 | if (!leftTrackSegment.isEmpty) {
71 | context.canvas.drawRect(leftTrackSegment, inactivePaint);
72 | }
73 |
74 | final activeRect = Rect.fromLTRB(
75 | trackCenter.dx, trackRect.top, thumbCenter.dx, trackRect.bottom);
76 | if (!activeRect.isEmpty) {
77 | context.canvas.drawRect(activeRect, activePaint);
78 | }
79 |
80 | final Rect rightTrackSegment = Rect.fromLTRB(
81 | thumbCenter.dx + thumbSize.width / 2,
82 | trackRect.top,
83 | trackRect.right,
84 | trackRect.bottom);
85 | if (!rightTrackSegment.isEmpty) {
86 | context.canvas.drawRect(rightTrackSegment, inactivePaint);
87 | }
88 | } else if (trackCenter.dx > thumbCenter.dx) {
89 | final Rect leftTrackSegment = Rect.fromLTRB(trackRect.left, trackRect.top,
90 | thumbCenter.dx + thumbSize.width / 2, trackRect.bottom);
91 | if (!leftTrackSegment.isEmpty) {
92 | context.canvas.drawRect(leftTrackSegment, inactivePaint);
93 | }
94 |
95 | final activeRect = Rect.fromLTRB(
96 | thumbCenter.dx + thumbSize.width / 2,
97 | trackRect.top,
98 | trackRect.center.dx,
99 | trackRect.bottom,
100 | );
101 | if (!activeRect.isEmpty) {
102 | context.canvas.drawRect(activeRect, activePaint);
103 | }
104 |
105 | final Rect rightTrackSegment = Rect.fromLTRB(
106 | max(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2),
107 | trackRect.top,
108 | trackRect.right,
109 | trackRect.bottom,
110 | );
111 |
112 | if (!rightTrackSegment.isEmpty) {
113 | context.canvas.drawRect(rightTrackSegment, inactivePaint);
114 | }
115 | } else {
116 | final Rect leftTrackSegment = Rect.fromLTRB(
117 | trackRect.left,
118 | trackRect.top,
119 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2),
120 | trackRect.bottom);
121 | if (!leftTrackSegment.isEmpty) {
122 | context.canvas.drawRect(leftTrackSegment, inactivePaint);
123 | }
124 |
125 | final Rect rightTrackSegment = Rect.fromLTRB(
126 | min(trackCenter.dx, thumbCenter.dx - thumbSize.width / 2),
127 | trackRect.top,
128 | trackRect.right,
129 | trackRect.bottom);
130 | if (!rightTrackSegment.isEmpty) {
131 | context.canvas.drawRect(rightTrackSegment, inactivePaint);
132 | }
133 | }
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/example/lib/main.dart:
--------------------------------------------------------------------------------
1 | import 'dart:ui' as ui;
2 | import 'package:app/centered_slider_track_shape.dart';
3 | import 'package:flutter/material.dart';
4 | import 'package:crop/crop.dart';
5 | import 'package:url_launcher/url_launcher.dart';
6 | import 'package:image_gallery_saver/image_gallery_saver.dart';
7 | import 'package:permission_handler/permission_handler.dart';
8 |
9 | void main() => runApp(const MyApp());
10 |
11 | class MyApp extends StatelessWidget {
12 | const MyApp({Key? key}) : super(key: key);
13 |
14 | @override
15 | Widget build(BuildContext context) {
16 | return MaterialApp(
17 | title: 'Crop Demo',
18 | theme: ThemeData(
19 | primarySwatch: Colors.blue,
20 | useMaterial3: true,
21 | brightness: Brightness.dark,
22 | ),
23 | home: const HomePage(),
24 | );
25 | }
26 | }
27 |
28 | class HomePage extends StatefulWidget {
29 | const HomePage({Key? key}) : super(key: key);
30 | @override
31 | State createState() => _HomePageState();
32 | }
33 |
34 | class _HomePageState extends State {
35 | final controller = CropController(aspectRatio: 1000 / 667.0);
36 | double _rotation = 0;
37 | BoxShape shape = BoxShape.rectangle;
38 |
39 | void _cropImage() async {
40 | final pixelRatio = MediaQuery.of(context).devicePixelRatio;
41 | final cropped = await controller.crop(pixelRatio: pixelRatio);
42 |
43 | if (cropped == null) {
44 | return;
45 | }
46 |
47 | if (!mounted) {
48 | return;
49 | }
50 |
51 | Navigator.of(context).push(
52 | MaterialPageRoute(
53 | builder: (context) => Scaffold(
54 | appBar: AppBar(
55 | title: const Text('Crop Result'),
56 | centerTitle: true,
57 | actions: [
58 | Builder(
59 | builder: (context) => IconButton(
60 | icon: const Icon(Icons.save),
61 | onPressed: () async {
62 | final status = await Permission.storage.request();
63 | if (status == PermissionStatus.granted) {
64 | await _saveScreenShot(cropped);
65 | if (!mounted) {
66 | return;
67 | }
68 | ScaffoldMessenger.of(context).showSnackBar(
69 | const SnackBar(
70 | content: Text('Saved to gallery.'),
71 | ),
72 | );
73 | }
74 | },
75 | ),
76 | ),
77 | ],
78 | ),
79 | body: Center(
80 | child: RawImage(
81 | image: cropped,
82 | ),
83 | ),
84 | ),
85 | fullscreenDialog: true,
86 | ),
87 | );
88 | }
89 |
90 | @override
91 | Widget build(BuildContext context) {
92 | final theme = Theme.of(context);
93 | return Scaffold(
94 | appBar: AppBar(
95 | title: const Text('Crop Demo'),
96 | centerTitle: true,
97 | leading: IconButton(
98 | icon: const Icon(Icons.link),
99 | onPressed: () {
100 | launchUrl(Uri.parse('https://github.com/xclud/flutter_crop'),
101 | mode: LaunchMode.externalApplication);
102 | },
103 | ),
104 | actions: [
105 | IconButton(
106 | onPressed: _cropImage,
107 | tooltip: 'Crop',
108 | icon: const Icon(Icons.crop),
109 | )
110 | ],
111 | ),
112 | body: Column(
113 | children: [
114 | Expanded(
115 | child: Container(
116 | color: Colors.black,
117 | padding: const EdgeInsets.all(8),
118 | child: Crop(
119 | onChanged: (decomposition) {
120 | if (_rotation != decomposition.rotation) {
121 | setState(() {
122 | _rotation = ((decomposition.rotation + 180) % 360) - 180;
123 | });
124 | }
125 |
126 | // print(
127 | // "Scale : ${decomposition.scale}, Rotation: ${decomposition.rotation}, translation: ${decomposition.translation}");
128 | },
129 | controller: controller,
130 | shape: shape,
131 | /* It's very important to set `fit: BoxFit.cover`.
132 | Do NOT remove this line.
133 | There are a lot of issues on github repo by people who remove this line and their image is not shown correctly.
134 | */
135 | foreground: IgnorePointer(
136 | child: Container(
137 | alignment: Alignment.bottomRight,
138 | child: const Text(
139 | 'Foreground Object',
140 | style: TextStyle(color: Colors.red),
141 | ),
142 | ),
143 | ),
144 | helper: shape == BoxShape.rectangle
145 | ? Container(
146 | decoration: BoxDecoration(
147 | border: Border.all(color: Colors.white, width: 2),
148 | ),
149 | )
150 | : null,
151 | child: Image.asset(
152 | 'images/sample.jpg',
153 | fit: BoxFit.cover,
154 | ),
155 | ),
156 | ),
157 | ),
158 | Row(
159 | children: [
160 | IconButton(
161 | icon: const Icon(Icons.undo),
162 | tooltip: 'Undo',
163 | onPressed: () {
164 | controller.rotation = 0;
165 | controller.scale = 1;
166 | controller.offset = Offset.zero;
167 | setState(() {
168 | _rotation = 0;
169 | });
170 | },
171 | ),
172 | Expanded(
173 | child: SliderTheme(
174 | data: theme.sliderTheme.copyWith(
175 | trackShape: CenteredRectangularSliderTrackShape(),
176 | ),
177 | child: Slider(
178 | divisions: 360,
179 | value: _rotation,
180 | min: -180,
181 | max: 180,
182 | label: '$_rotation°',
183 | onChanged: (n) {
184 | setState(() {
185 | _rotation = n.roundToDouble();
186 | controller.rotation = _rotation;
187 | });
188 | },
189 | ),
190 | ),
191 | ),
192 | PopupMenuButton(
193 | icon: const Icon(Icons.crop_free),
194 | itemBuilder: (context) => [
195 | const PopupMenuItem(
196 | value: BoxShape.rectangle,
197 | child: Text("Box"),
198 | ),
199 | const PopupMenuItem(
200 | value: BoxShape.circle,
201 | child: Text("Oval"),
202 | ),
203 | ],
204 | tooltip: 'Crop Shape',
205 | onSelected: (x) {
206 | setState(() {
207 | shape = x;
208 | });
209 | },
210 | ),
211 | PopupMenuButton(
212 | icon: const Icon(Icons.aspect_ratio),
213 | itemBuilder: (context) => [
214 | const PopupMenuItem(
215 | value: 1000 / 667.0,
216 | child: Text("Original"),
217 | ),
218 | const PopupMenuDivider(),
219 | const PopupMenuItem(
220 | value: 16.0 / 9.0,
221 | child: Text("16:9"),
222 | ),
223 | const PopupMenuItem(
224 | value: 4.0 / 3.0,
225 | child: Text("4:3"),
226 | ),
227 | const PopupMenuItem(
228 | value: 1,
229 | child: Text("1:1"),
230 | ),
231 | const PopupMenuItem(
232 | value: 3.0 / 4.0,
233 | child: Text("3:4"),
234 | ),
235 | const PopupMenuItem(
236 | value: 9.0 / 16.0,
237 | child: Text("9:16"),
238 | ),
239 | ],
240 | tooltip: 'Aspect Ratio',
241 | onSelected: (x) {
242 | controller.aspectRatio = x;
243 | setState(() {});
244 | },
245 | ),
246 | ],
247 | ),
248 | ],
249 | ),
250 | );
251 | }
252 | }
253 |
254 | Future _saveScreenShot(ui.Image img) async {
255 | var byteData = await img.toByteData(format: ui.ImageByteFormat.png);
256 | var buffer = byteData!.buffer.asUint8List();
257 | final result = await ImageGallerySaver.saveImage(buffer);
258 |
259 | return result;
260 | }
261 |
--------------------------------------------------------------------------------
/lib/src/crop.dart:
--------------------------------------------------------------------------------
1 | part of crop;
2 |
3 | /// Used for cropping the [child] widget.
4 | class Crop extends StatefulWidget {
5 | /// The constructor.
6 | const Crop({
7 | Key? key,
8 | required this.child,
9 | required this.controller,
10 | this.padding = const EdgeInsets.all(8),
11 | this.dimColor = const Color.fromRGBO(0, 0, 0, 0.8),
12 | this.backgroundColor = Colors.black,
13 | this.background,
14 | this.foreground,
15 | this.helper,
16 | this.overlay,
17 | this.interactive = true,
18 | this.shape = BoxShape.rectangle,
19 | this.onChanged,
20 | this.animationDuration = const Duration(milliseconds: 200),
21 | this.radius,
22 | }) : super(key: key);
23 |
24 | /// The widget below this widget in the tree.
25 | final Widget child;
26 |
27 | /// Controls the crop area.
28 | final CropController controller;
29 |
30 | /// Background color of the crop area.
31 | final Color backgroundColor;
32 |
33 | /// Dim color of the crop area.
34 | final Color dimColor;
35 |
36 | /// Padding of the crop area.
37 | final EdgeInsets padding;
38 |
39 | /// Background widget displayed on under on the resulting image.
40 | final Widget? background;
41 |
42 | /// Forground widget is displayed on top on the resulting image.
43 | final Widget? foreground;
44 |
45 | /// Helper widget is displayed on top on the crop
46 | /// area, but not included in the resulting image.
47 | ///
48 | /// Useful to display helper lines e.g. golden ratio.
49 | final Widget? helper;
50 |
51 | /// Similar to [helper] but is not transformed.
52 | final Widget? overlay;
53 |
54 | /// If set to false, the widget will not listen for gestures.
55 | final bool interactive;
56 |
57 | /// Shape of the crop area.
58 | final BoxShape shape;
59 |
60 | /// Triggered when a gesture is detected.
61 | final ValueChanged? onChanged;
62 |
63 | /// When dragged out of crop area boundries, it will
64 | /// re-center. This sets the re-center duration.
65 | final Duration animationDuration;
66 |
67 | /// Radius of the crop area.
68 | final Radius? radius;
69 |
70 | @override
71 | State createState() {
72 | return _CropState();
73 | }
74 |
75 | @override
76 | void debugFillProperties(DiagnosticPropertiesBuilder properties) {
77 | super.debugFillProperties(properties);
78 | properties.add(DiagnosticsProperty('padding', padding));
79 | properties.add(ColorProperty('dimColor', dimColor));
80 | properties.add(DiagnosticsProperty('child', child));
81 | properties.add(DiagnosticsProperty('controller', controller));
82 | properties.add(DiagnosticsProperty('background', background));
83 | properties.add(DiagnosticsProperty('foreground', foreground));
84 | properties.add(DiagnosticsProperty('helper', helper));
85 | properties.add(DiagnosticsProperty('overlay', overlay));
86 | properties.add(FlagProperty(
87 | 'interactive',
88 | value: interactive,
89 | ifTrue: 'enabled',
90 | ifFalse: 'disabled',
91 | showName: true,
92 | ));
93 | }
94 | }
95 |
96 | class _CropState extends State with TickerProviderStateMixin {
97 | final _key = GlobalKey();
98 | final _parent = GlobalKey();
99 | final _repaintBoundaryKey = GlobalKey();
100 |
101 | double _previousScale = 1;
102 | Offset _previousOffset = Offset.zero;
103 | Offset _startOffset = Offset.zero;
104 | Offset _endOffset = Offset.zero;
105 | double _previousGestureRotation = 0.0;
106 |
107 | /// Store the pointer count (finger involved to perform scaling).
108 | ///
109 | /// This is used to compare with the value in
110 | /// [ScaleUpdateDetails.pointerCount]. Check [_onScaleUpdate] for detail.
111 | int _previousPointerCount = 0;
112 |
113 | late AnimationController _controller;
114 | late CurvedAnimation _animation;
115 |
116 | Future _crop(double pixelRatio) {
117 | final rrb = _repaintBoundaryKey.currentContext?.findRenderObject()
118 | as RenderRepaintBoundary;
119 |
120 | return rrb.toImage(pixelRatio: pixelRatio);
121 | }
122 |
123 | @override
124 | void initState() {
125 | widget.controller._cropCallback = _crop;
126 | widget.controller.addListener(_reCenterImage);
127 |
128 | //Setup animation.
129 | _controller = AnimationController(
130 | vsync: this,
131 | duration: widget.animationDuration,
132 | );
133 |
134 | _animation = CurvedAnimation(curve: Curves.easeInOut, parent: _controller);
135 | _animation.addListener(() {
136 | if (_animation.isCompleted) {
137 | _reCenterImage(false);
138 | }
139 | setState(() {});
140 | });
141 | super.initState();
142 | }
143 |
144 | void _reCenterImage([bool animate = true]) {
145 | //final totalSize = _parent.currentContext.size;
146 |
147 | final sz = _key.currentContext!.size!;
148 | final s = widget.controller._scale * widget.controller._getMinScale();
149 | final w = sz.width;
150 | final h = sz.height;
151 | final offset = _toVector2(widget.controller._offset);
152 | final canvas = Rectangle.fromLTWH(0, 0, w, h);
153 | final obb = Obb2(
154 | center: offset + canvas.center,
155 | width: w * s,
156 | height: h * s,
157 | rotation: widget.controller._rotation,
158 | );
159 |
160 | final bakedObb = obb.bake();
161 |
162 | _startOffset = widget.controller._offset;
163 | _endOffset = widget.controller._offset;
164 |
165 | final ctl = canvas.topLeft;
166 | final ctr = canvas.topRight;
167 | final cbr = canvas.bottomRight;
168 | final cbl = canvas.bottomLeft;
169 |
170 | final ll = Line(bakedObb.topLeft, bakedObb.bottomLeft);
171 | final tt = Line(bakedObb.topRight, bakedObb.topLeft);
172 | final rr = Line(bakedObb.bottomRight, bakedObb.topRight);
173 | final bb = Line(bakedObb.bottomLeft, bakedObb.bottomRight);
174 |
175 | final tl = ll.project(ctl);
176 | final tr = tt.project(ctr);
177 | final br = rr.project(cbr);
178 | final bl = bb.project(cbl);
179 |
180 | final dtl = ll.distanceToPoint(ctl);
181 | final dtr = tt.distanceToPoint(ctr);
182 | final dbr = rr.distanceToPoint(cbr);
183 | final dbl = bb.distanceToPoint(cbl);
184 |
185 | if (dtl > 0) {
186 | final d = _toOffset(ctl - tl);
187 | _endOffset += d;
188 | }
189 |
190 | if (dtr > 0) {
191 | final d = _toOffset(ctr - tr);
192 | _endOffset += d;
193 | }
194 |
195 | if (dbr > 0) {
196 | final d = _toOffset(cbr - br);
197 | _endOffset += d;
198 | }
199 | if (dbl > 0) {
200 | final d = _toOffset(cbl - bl);
201 | _endOffset += d;
202 | }
203 |
204 | widget.controller._offset = _endOffset;
205 |
206 | if (animate) {
207 | if (_controller.isCompleted || _controller.isAnimating) {
208 | _controller.reset();
209 | }
210 | _controller.forward();
211 | } else {
212 | _startOffset = _endOffset;
213 | }
214 |
215 | setState(() {});
216 | _handleOnChanged();
217 | }
218 |
219 | void _onScaleUpdate(ScaleUpdateDetails details) {
220 | widget.controller._offset += details.focalPoint - _previousOffset;
221 | _previousOffset = details.focalPoint;
222 | widget.controller._scale = _previousScale * details.scale;
223 | _startOffset = widget.controller._offset;
224 | _endOffset = widget.controller._offset;
225 |
226 | // In the case where lesser than 2 fingers involved in scaling, we ignore
227 | // the rotation handling.
228 | if (details.pointerCount > 1) {
229 | // In the first touch, we reset all the values.
230 | if (_previousPointerCount != details.pointerCount) {
231 | _previousPointerCount = details.pointerCount;
232 | _previousGestureRotation = 0.0;
233 | }
234 |
235 | // Instead of directly embracing the details.rotation, we need to
236 | // perform calculation to ensure that each round of rotation is smooth.
237 | // A user rotate the image using finger and release is considered as a
238 | // round. Without this calculation, the rotation degree of the image will
239 | // be reset.
240 | final gestureRotation = vm.degrees(details.rotation);
241 |
242 | // Within a round of rotation, the details.rotation is provided with
243 | // incremented value when user rotates. We don't need this, all we
244 | // want is the offset.
245 | final gestureRotationOffset = _previousGestureRotation - gestureRotation;
246 |
247 | // Remove the offset and constraint the degree scope to 0° <= degree <=
248 | // 360°. Constraint the scope is unnecessary, however, by doing this,
249 | // it would make our life easier when debugging.
250 | final rotationAfterCalculation =
251 | (widget.controller.rotation - gestureRotationOffset) % 360;
252 |
253 | /* details.rotation is in radians, convert this to degrees and set
254 | our rotation */
255 | widget.controller._rotation = rotationAfterCalculation;
256 | _previousGestureRotation = gestureRotation;
257 | }
258 |
259 | setState(() {});
260 | _handleOnChanged();
261 | }
262 |
263 | void _handleOnChanged() {
264 | widget.onChanged?.call(MatrixDecomposition(
265 | scale: widget.controller.scale,
266 | rotation: widget.controller.rotation,
267 | translation: widget.controller._offset));
268 | }
269 |
270 | @override
271 | Widget build(BuildContext context) {
272 | final r = vm.radians(widget.controller._rotation);
273 | final s = widget.controller._scale * widget.controller._getMinScale();
274 | final o = Offset.lerp(_startOffset, _endOffset, _animation.value)!;
275 |
276 | Widget buildInnerCanvas() {
277 | final ip = IgnorePointer(
278 | key: _key,
279 | child: Transform(
280 | alignment: Alignment.center,
281 | transform: Matrix4.identity()
282 | ..translate(o.dx, o.dy, 0)
283 | ..rotateZ(r)
284 | ..scale(s, s, 1),
285 | child: FittedBox(
286 | fit: BoxFit.cover,
287 | child: widget.child,
288 | ),
289 | ),
290 | );
291 |
292 | List widgets = [];
293 |
294 | if (widget.background != null) {
295 | widgets.add(widget.background!);
296 | }
297 |
298 | widgets.add(ip);
299 |
300 | if (widget.foreground != null) {
301 | widgets.add(widget.foreground!);
302 | }
303 |
304 | if (widgets.length == 1) {
305 | return ip;
306 | } else {
307 | return Stack(
308 | fit: StackFit.expand,
309 | children: widgets,
310 | );
311 | }
312 | }
313 |
314 | Widget buildRepaintBoundary() {
315 | final repaint = RepaintBoundary(
316 | key: _repaintBoundaryKey,
317 | child: buildInnerCanvas(),
318 | );
319 |
320 | final helper = widget.helper;
321 |
322 | if (helper == null) {
323 | return repaint;
324 | }
325 |
326 | return Stack(
327 | fit: StackFit.expand,
328 | children: [repaint, helper],
329 | );
330 | }
331 |
332 | final gd = GestureDetector(
333 | onScaleStart: (details) {
334 | _previousOffset = details.focalPoint;
335 | _previousScale = max(widget.controller._scale, 1);
336 | },
337 | onScaleUpdate: _onScaleUpdate,
338 | onScaleEnd: (details) {
339 | widget.controller._scale = max(widget.controller._scale, 1);
340 | _previousPointerCount = 0;
341 | _reCenterImage();
342 | },
343 | );
344 |
345 | List over = [
346 | CropRenderObjectWidget(
347 | aspectRatio: widget.controller._aspectRatio,
348 | backgroundColor: widget.backgroundColor,
349 | shape: widget.shape,
350 | dimColor: widget.dimColor,
351 | padding: widget.padding,
352 | radius: widget.radius,
353 | child: buildRepaintBoundary(),
354 | ),
355 | ];
356 |
357 | if (widget.overlay != null) {
358 | over.add(widget.overlay!);
359 | }
360 |
361 | if (widget.interactive) {
362 | over.add(gd);
363 | }
364 |
365 | return ClipRect(
366 | key: _parent,
367 | child: Stack(
368 | fit: StackFit.expand,
369 | children: over,
370 | ),
371 | );
372 | }
373 |
374 | @override
375 | void dispose() {
376 | _controller.dispose();
377 | widget.controller.removeListener(_reCenterImage);
378 | super.dispose();
379 | }
380 | }
381 |
--------------------------------------------------------------------------------
/example/ios/Runner.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 54;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
11 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
12 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
13 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
14 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
15 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
16 | /* End PBXBuildFile section */
17 |
18 | /* Begin PBXCopyFilesBuildPhase section */
19 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = {
20 | isa = PBXCopyFilesBuildPhase;
21 | buildActionMask = 2147483647;
22 | dstPath = "";
23 | dstSubfolderSpec = 10;
24 | files = (
25 | );
26 | name = "Embed Frameworks";
27 | runOnlyForDeploymentPostprocessing = 0;
28 | };
29 | /* End PBXCopyFilesBuildPhase section */
30 |
31 | /* Begin PBXFileReference section */
32 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; };
33 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; };
34 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; };
35 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; };
36 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
37 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; };
38 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; };
39 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; };
40 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
41 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
42 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
43 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
44 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
45 | /* End PBXFileReference section */
46 |
47 | /* Begin PBXFrameworksBuildPhase section */
48 | 97C146EB1CF9000F007C117D /* Frameworks */ = {
49 | isa = PBXFrameworksBuildPhase;
50 | buildActionMask = 2147483647;
51 | files = (
52 | );
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXFrameworksBuildPhase section */
56 |
57 | /* Begin PBXGroup section */
58 | 9740EEB11CF90186004384FC /* Flutter */ = {
59 | isa = PBXGroup;
60 | children = (
61 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
62 | 9740EEB21CF90195004384FC /* Debug.xcconfig */,
63 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
64 | 9740EEB31CF90195004384FC /* Generated.xcconfig */,
65 | );
66 | name = Flutter;
67 | sourceTree = "";
68 | };
69 | 97C146E51CF9000F007C117D = {
70 | isa = PBXGroup;
71 | children = (
72 | 9740EEB11CF90186004384FC /* Flutter */,
73 | 97C146F01CF9000F007C117D /* Runner */,
74 | 97C146EF1CF9000F007C117D /* Products */,
75 | );
76 | sourceTree = "";
77 | };
78 | 97C146EF1CF9000F007C117D /* Products */ = {
79 | isa = PBXGroup;
80 | children = (
81 | 97C146EE1CF9000F007C117D /* Runner.app */,
82 | );
83 | name = Products;
84 | sourceTree = "";
85 | };
86 | 97C146F01CF9000F007C117D /* Runner */ = {
87 | isa = PBXGroup;
88 | children = (
89 | 97C146FA1CF9000F007C117D /* Main.storyboard */,
90 | 97C146FD1CF9000F007C117D /* Assets.xcassets */,
91 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
92 | 97C147021CF9000F007C117D /* Info.plist */,
93 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
94 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
95 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
96 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
97 | );
98 | path = Runner;
99 | sourceTree = "";
100 | };
101 | /* End PBXGroup section */
102 |
103 | /* Begin PBXNativeTarget section */
104 | 97C146ED1CF9000F007C117D /* Runner */ = {
105 | isa = PBXNativeTarget;
106 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
107 | buildPhases = (
108 | 9740EEB61CF901F6004384FC /* Run Script */,
109 | 97C146EA1CF9000F007C117D /* Sources */,
110 | 97C146EB1CF9000F007C117D /* Frameworks */,
111 | 97C146EC1CF9000F007C117D /* Resources */,
112 | 9705A1C41CF9048500538489 /* Embed Frameworks */,
113 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */,
114 | );
115 | buildRules = (
116 | );
117 | dependencies = (
118 | );
119 | name = Runner;
120 | productName = Runner;
121 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
122 | productType = "com.apple.product-type.application";
123 | };
124 | /* End PBXNativeTarget section */
125 |
126 | /* Begin PBXProject section */
127 | 97C146E61CF9000F007C117D /* Project object */ = {
128 | isa = PBXProject;
129 | attributes = {
130 | LastUpgradeCheck = 1300;
131 | ORGANIZATIONNAME = "";
132 | TargetAttributes = {
133 | 97C146ED1CF9000F007C117D = {
134 | CreatedOnToolsVersion = 7.3.1;
135 | LastSwiftMigration = 1100;
136 | };
137 | };
138 | };
139 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
140 | compatibilityVersion = "Xcode 9.3";
141 | developmentRegion = en;
142 | hasScannedForEncodings = 0;
143 | knownRegions = (
144 | en,
145 | Base,
146 | );
147 | mainGroup = 97C146E51CF9000F007C117D;
148 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
149 | projectDirPath = "";
150 | projectRoot = "";
151 | targets = (
152 | 97C146ED1CF9000F007C117D /* Runner */,
153 | );
154 | };
155 | /* End PBXProject section */
156 |
157 | /* Begin PBXResourcesBuildPhase section */
158 | 97C146EC1CF9000F007C117D /* Resources */ = {
159 | isa = PBXResourcesBuildPhase;
160 | buildActionMask = 2147483647;
161 | files = (
162 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
163 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
164 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
165 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
166 | );
167 | runOnlyForDeploymentPostprocessing = 0;
168 | };
169 | /* End PBXResourcesBuildPhase section */
170 |
171 | /* Begin PBXShellScriptBuildPhase section */
172 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
173 | isa = PBXShellScriptBuildPhase;
174 | alwaysOutOfDate = 1;
175 | buildActionMask = 2147483647;
176 | files = (
177 | );
178 | inputPaths = (
179 | );
180 | name = "Thin Binary";
181 | outputPaths = (
182 | );
183 | runOnlyForDeploymentPostprocessing = 0;
184 | shellPath = /bin/sh;
185 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
186 | };
187 | 9740EEB61CF901F6004384FC /* Run Script */ = {
188 | isa = PBXShellScriptBuildPhase;
189 | alwaysOutOfDate = 1;
190 | buildActionMask = 2147483647;
191 | files = (
192 | );
193 | inputPaths = (
194 | );
195 | name = "Run Script";
196 | outputPaths = (
197 | );
198 | runOnlyForDeploymentPostprocessing = 0;
199 | shellPath = /bin/sh;
200 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
201 | };
202 | /* End PBXShellScriptBuildPhase section */
203 |
204 | /* Begin PBXSourcesBuildPhase section */
205 | 97C146EA1CF9000F007C117D /* Sources */ = {
206 | isa = PBXSourcesBuildPhase;
207 | buildActionMask = 2147483647;
208 | files = (
209 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
210 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
211 | );
212 | runOnlyForDeploymentPostprocessing = 0;
213 | };
214 | /* End PBXSourcesBuildPhase section */
215 |
216 | /* Begin PBXVariantGroup section */
217 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = {
218 | isa = PBXVariantGroup;
219 | children = (
220 | 97C146FB1CF9000F007C117D /* Base */,
221 | );
222 | name = Main.storyboard;
223 | sourceTree = "";
224 | };
225 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
226 | isa = PBXVariantGroup;
227 | children = (
228 | 97C147001CF9000F007C117D /* Base */,
229 | );
230 | name = LaunchScreen.storyboard;
231 | sourceTree = "";
232 | };
233 | /* End PBXVariantGroup section */
234 |
235 | /* Begin XCBuildConfiguration section */
236 | 249021D3217E4FDB00AE95B9 /* Profile */ = {
237 | isa = XCBuildConfiguration;
238 | buildSettings = {
239 | ALWAYS_SEARCH_USER_PATHS = NO;
240 | CLANG_ANALYZER_NONNULL = YES;
241 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
242 | CLANG_CXX_LIBRARY = "libc++";
243 | CLANG_ENABLE_MODULES = YES;
244 | CLANG_ENABLE_OBJC_ARC = YES;
245 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
246 | CLANG_WARN_BOOL_CONVERSION = YES;
247 | CLANG_WARN_COMMA = YES;
248 | CLANG_WARN_CONSTANT_CONVERSION = YES;
249 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
250 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
251 | CLANG_WARN_EMPTY_BODY = YES;
252 | CLANG_WARN_ENUM_CONVERSION = YES;
253 | CLANG_WARN_INFINITE_RECURSION = YES;
254 | CLANG_WARN_INT_CONVERSION = YES;
255 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
256 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
257 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
258 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
259 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
260 | CLANG_WARN_STRICT_PROTOTYPES = YES;
261 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
262 | CLANG_WARN_UNREACHABLE_CODE = YES;
263 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
264 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
265 | COPY_PHASE_STRIP = NO;
266 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
267 | ENABLE_NS_ASSERTIONS = NO;
268 | ENABLE_STRICT_OBJC_MSGSEND = YES;
269 | GCC_C_LANGUAGE_STANDARD = gnu99;
270 | GCC_NO_COMMON_BLOCKS = YES;
271 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
272 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
273 | GCC_WARN_UNDECLARED_SELECTOR = YES;
274 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
275 | GCC_WARN_UNUSED_FUNCTION = YES;
276 | GCC_WARN_UNUSED_VARIABLE = YES;
277 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
278 | MTL_ENABLE_DEBUG_INFO = NO;
279 | SDKROOT = iphoneos;
280 | SUPPORTED_PLATFORMS = iphoneos;
281 | TARGETED_DEVICE_FAMILY = "1,2";
282 | VALIDATE_PRODUCT = YES;
283 | };
284 | name = Profile;
285 | };
286 | 249021D4217E4FDB00AE95B9 /* Profile */ = {
287 | isa = XCBuildConfiguration;
288 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
289 | buildSettings = {
290 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
291 | CLANG_ENABLE_MODULES = YES;
292 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
293 | ENABLE_BITCODE = NO;
294 | INFOPLIST_FILE = Runner/Info.plist;
295 | LD_RUNPATH_SEARCH_PATHS = (
296 | "$(inherited)",
297 | "@executable_path/Frameworks",
298 | );
299 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app;
300 | PRODUCT_NAME = "$(TARGET_NAME)";
301 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
302 | SWIFT_VERSION = 5.0;
303 | VERSIONING_SYSTEM = "apple-generic";
304 | };
305 | name = Profile;
306 | };
307 | 97C147031CF9000F007C117D /* Debug */ = {
308 | isa = XCBuildConfiguration;
309 | buildSettings = {
310 | ALWAYS_SEARCH_USER_PATHS = NO;
311 | CLANG_ANALYZER_NONNULL = YES;
312 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
313 | CLANG_CXX_LIBRARY = "libc++";
314 | CLANG_ENABLE_MODULES = YES;
315 | CLANG_ENABLE_OBJC_ARC = YES;
316 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
317 | CLANG_WARN_BOOL_CONVERSION = YES;
318 | CLANG_WARN_COMMA = YES;
319 | CLANG_WARN_CONSTANT_CONVERSION = YES;
320 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
321 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
322 | CLANG_WARN_EMPTY_BODY = YES;
323 | CLANG_WARN_ENUM_CONVERSION = YES;
324 | CLANG_WARN_INFINITE_RECURSION = YES;
325 | CLANG_WARN_INT_CONVERSION = YES;
326 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
327 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
328 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
329 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
330 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
331 | CLANG_WARN_STRICT_PROTOTYPES = YES;
332 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
333 | CLANG_WARN_UNREACHABLE_CODE = YES;
334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
335 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
336 | COPY_PHASE_STRIP = NO;
337 | DEBUG_INFORMATION_FORMAT = dwarf;
338 | ENABLE_STRICT_OBJC_MSGSEND = YES;
339 | ENABLE_TESTABILITY = YES;
340 | GCC_C_LANGUAGE_STANDARD = gnu99;
341 | GCC_DYNAMIC_NO_PIC = NO;
342 | GCC_NO_COMMON_BLOCKS = YES;
343 | GCC_OPTIMIZATION_LEVEL = 0;
344 | GCC_PREPROCESSOR_DEFINITIONS = (
345 | "DEBUG=1",
346 | "$(inherited)",
347 | );
348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
350 | GCC_WARN_UNDECLARED_SELECTOR = YES;
351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
352 | GCC_WARN_UNUSED_FUNCTION = YES;
353 | GCC_WARN_UNUSED_VARIABLE = YES;
354 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
355 | MTL_ENABLE_DEBUG_INFO = YES;
356 | ONLY_ACTIVE_ARCH = YES;
357 | SDKROOT = iphoneos;
358 | TARGETED_DEVICE_FAMILY = "1,2";
359 | };
360 | name = Debug;
361 | };
362 | 97C147041CF9000F007C117D /* Release */ = {
363 | isa = XCBuildConfiguration;
364 | buildSettings = {
365 | ALWAYS_SEARCH_USER_PATHS = NO;
366 | CLANG_ANALYZER_NONNULL = YES;
367 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
368 | CLANG_CXX_LIBRARY = "libc++";
369 | CLANG_ENABLE_MODULES = YES;
370 | CLANG_ENABLE_OBJC_ARC = YES;
371 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
372 | CLANG_WARN_BOOL_CONVERSION = YES;
373 | CLANG_WARN_COMMA = YES;
374 | CLANG_WARN_CONSTANT_CONVERSION = YES;
375 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
376 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
377 | CLANG_WARN_EMPTY_BODY = YES;
378 | CLANG_WARN_ENUM_CONVERSION = YES;
379 | CLANG_WARN_INFINITE_RECURSION = YES;
380 | CLANG_WARN_INT_CONVERSION = YES;
381 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
382 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
383 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
384 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
385 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
386 | CLANG_WARN_STRICT_PROTOTYPES = YES;
387 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
388 | CLANG_WARN_UNREACHABLE_CODE = YES;
389 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
390 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
391 | COPY_PHASE_STRIP = NO;
392 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
393 | ENABLE_NS_ASSERTIONS = NO;
394 | ENABLE_STRICT_OBJC_MSGSEND = YES;
395 | GCC_C_LANGUAGE_STANDARD = gnu99;
396 | GCC_NO_COMMON_BLOCKS = YES;
397 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
398 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
399 | GCC_WARN_UNDECLARED_SELECTOR = YES;
400 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
401 | GCC_WARN_UNUSED_FUNCTION = YES;
402 | GCC_WARN_UNUSED_VARIABLE = YES;
403 | IPHONEOS_DEPLOYMENT_TARGET = 11.0;
404 | MTL_ENABLE_DEBUG_INFO = NO;
405 | SDKROOT = iphoneos;
406 | SUPPORTED_PLATFORMS = iphoneos;
407 | SWIFT_COMPILATION_MODE = wholemodule;
408 | SWIFT_OPTIMIZATION_LEVEL = "-O";
409 | TARGETED_DEVICE_FAMILY = "1,2";
410 | VALIDATE_PRODUCT = YES;
411 | };
412 | name = Release;
413 | };
414 | 97C147061CF9000F007C117D /* Debug */ = {
415 | isa = XCBuildConfiguration;
416 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
417 | buildSettings = {
418 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
419 | CLANG_ENABLE_MODULES = YES;
420 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
421 | ENABLE_BITCODE = NO;
422 | INFOPLIST_FILE = Runner/Info.plist;
423 | LD_RUNPATH_SEARCH_PATHS = (
424 | "$(inherited)",
425 | "@executable_path/Frameworks",
426 | );
427 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app;
428 | PRODUCT_NAME = "$(TARGET_NAME)";
429 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
430 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
431 | SWIFT_VERSION = 5.0;
432 | VERSIONING_SYSTEM = "apple-generic";
433 | };
434 | name = Debug;
435 | };
436 | 97C147071CF9000F007C117D /* Release */ = {
437 | isa = XCBuildConfiguration;
438 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
439 | buildSettings = {
440 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
441 | CLANG_ENABLE_MODULES = YES;
442 | CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
443 | ENABLE_BITCODE = NO;
444 | INFOPLIST_FILE = Runner/Info.plist;
445 | LD_RUNPATH_SEARCH_PATHS = (
446 | "$(inherited)",
447 | "@executable_path/Frameworks",
448 | );
449 | PRODUCT_BUNDLE_IDENTIFIER = dev.pub.crop.app;
450 | PRODUCT_NAME = "$(TARGET_NAME)";
451 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
452 | SWIFT_VERSION = 5.0;
453 | VERSIONING_SYSTEM = "apple-generic";
454 | };
455 | name = Release;
456 | };
457 | /* End XCBuildConfiguration section */
458 |
459 | /* Begin XCConfigurationList section */
460 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
461 | isa = XCConfigurationList;
462 | buildConfigurations = (
463 | 97C147031CF9000F007C117D /* Debug */,
464 | 97C147041CF9000F007C117D /* Release */,
465 | 249021D3217E4FDB00AE95B9 /* Profile */,
466 | );
467 | defaultConfigurationIsVisible = 0;
468 | defaultConfigurationName = Release;
469 | };
470 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
471 | isa = XCConfigurationList;
472 | buildConfigurations = (
473 | 97C147061CF9000F007C117D /* Debug */,
474 | 97C147071CF9000F007C117D /* Release */,
475 | 249021D4217E4FDB00AE95B9 /* Profile */,
476 | );
477 | defaultConfigurationIsVisible = 0;
478 | defaultConfigurationName = Release;
479 | };
480 | /* End XCConfigurationList section */
481 | };
482 | rootObject = 97C146E61CF9000F007C117D /* Project object */;
483 | }
484 |
--------------------------------------------------------------------------------