├── ios ├── Assets │ └── .gitkeep ├── Classes │ ├── MultiImagePickerPlugin.h │ ├── MultiImagePickerPlugin.m │ └── SwiftMultiImagePickerPlugin.swift ├── .gitignore └── multi_image_picker.podspec ├── android ├── gradle.properties ├── settings.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── .gitignore ├── src │ └── main │ │ ├── AndroidManifest.xml │ │ └── java │ │ └── com │ │ └── vitanov │ │ └── multiimagepicker │ │ ├── FileDirectory.java │ │ └── MultiImagePickerPlugin.java ├── build.gradle ├── gradlew.bat └── gradlew ├── example ├── android │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.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 │ │ │ │ ├── values │ │ │ │ │ └── styles.xml │ │ │ │ └── drawable │ │ │ │ │ └── launch_background.xml │ │ │ │ ├── java │ │ │ │ └── com │ │ │ │ │ └── vitanov │ │ │ │ │ └── multiimagepickerexample │ │ │ │ │ └── MainActivity.java │ │ │ │ └── AndroidManifest.xml │ │ └── build.gradle │ ├── .gitignore │ ├── settings.gradle │ ├── build.gradle │ ├── gradlew.bat │ └── gradlew ├── ios │ ├── 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 │ ├── Flutter │ │ ├── Debug.xcconfig │ │ ├── Release.xcconfig │ │ └── AppFrameworkInfo.plist │ ├── Runner.xcodeproj │ │ ├── project.xcworkspace │ │ │ └── contents.xcworkspacedata │ │ ├── xcshareddata │ │ │ └── xcschemes │ │ │ │ └── Runner.xcscheme │ │ └── project.pbxproj │ ├── Runner.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ ├── .gitignore │ ├── Podfile.lock │ └── Podfile ├── .gitignore ├── README.md ├── .metadata ├── multi_image_picker_example.iml ├── lib │ ├── asset_view.dart │ └── main.dart ├── multi_image_picker_example_android.iml └── pubspec.yaml ├── screenshots ├── ios-1.png ├── ios-2.png ├── ios-3.png ├── android-1.png ├── android-2.png └── android-3.png ├── .gitignore ├── lib ├── multi_image_picker.dart ├── cupertino_options.dart ├── picker.dart └── asset.dart ├── .travis.yml ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── pubspec.yaml ├── doc ├── ios.md └── android.md ├── multi_image_picker.iml ├── LICENSE ├── multi_image_picker_android.iml ├── test ├── asset_test.dart └── multi_image_picker_test.dart ├── README.md └── CHANGELOG.md /ios/Assets/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'multi_image_picker' 2 | -------------------------------------------------------------------------------- /example/android/gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.jvmargs=-Xmx1536M 2 | -------------------------------------------------------------------------------- /example/ios/Runner/Runner-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import "GeneratedPluginRegistrant.h" -------------------------------------------------------------------------------- /screenshots/ios-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/ios-1.png -------------------------------------------------------------------------------- /screenshots/ios-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/ios-2.png -------------------------------------------------------------------------------- /screenshots/ios-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/ios-3.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | pubspec.lock 7 | 8 | build/ 9 | .vscode -------------------------------------------------------------------------------- /lib/multi_image_picker.dart: -------------------------------------------------------------------------------- 1 | export 'asset.dart'; 2 | export 'cupertino_options.dart'; 3 | export 'picker.dart'; 4 | -------------------------------------------------------------------------------- /screenshots/android-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/android-1.png -------------------------------------------------------------------------------- /screenshots/android-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/android-2.png -------------------------------------------------------------------------------- /screenshots/android-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/screenshots/android-3.png -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .dart_tool/ 3 | 4 | .packages 5 | .pub/ 6 | 7 | build/ 8 | 9 | .flutter-plugins 10 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /example/ios/Flutter/Debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/Classes/MultiImagePickerPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | @interface MultiImagePickerPlugin : NSObject 4 | @end 5 | -------------------------------------------------------------------------------- /example/ios/Flutter/Release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" 2 | #include "Generated.xcconfig" 3 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/workspace.xml 6 | /.idea/libraries 7 | .DS_Store 8 | /build 9 | /captures 10 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/HEAD/example/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/HEAD/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png -------------------------------------------------------------------------------- /example/android/.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | *.class 3 | .gradle 4 | .idea 5 | /local.properties 6 | /.idea/workspace.xml 7 | /.idea/libraries 8 | .DS_Store 9 | /build 10 | /captures 11 | GeneratedPluginRegistrant.java 12 | -------------------------------------------------------------------------------- /example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-1024x1024@1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CircleCurve/multi_image_picker/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/CircleCurve/multi_image_picker/HEAD/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | # multi_image_picker_example 2 | 3 | Demonstrates how to use the multi_image_picker plugin. 4 | 5 | ## Getting Started 6 | 7 | For help getting started with Flutter, view our online 8 | [documentation](https://flutter.io/). 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Aug 04 14:54:34 EEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /example/android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Jun 23 08:50:38 CEST 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 7 | -------------------------------------------------------------------------------- /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 and should not be manually edited. 5 | 6 | version: 7 | revision: c7ea3ca377e909469c68f2ab878a5bc53d3cf66b 8 | channel: beta 9 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Classes/MultiImagePickerPlugin.m: -------------------------------------------------------------------------------- 1 | #import "MultiImagePickerPlugin.h" 2 | #import 3 | 4 | @implementation MultiImagePickerPlugin 5 | + (void)registerWithRegistrar:(NSObject*)registrar { 6 | [SwiftMultiImagePickerPlugin registerWithRegistrar:registrar]; 7 | } 8 | @end 9 | -------------------------------------------------------------------------------- /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. -------------------------------------------------------------------------------- /example/android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 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: [UIApplicationLaunchOptionsKey: Any]? 9 | ) -> Bool { 10 | GeneratedPluginRegistrant.register(with: self) 11 | return super.application(application, didFinishLaunchingWithOptions: launchOptions) 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /example/android/app/src/main/res/drawable/launch_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/Generated.xcconfig 37 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: 2 | - linux 3 | sudo: false 4 | addons: 5 | apt: 6 | # Flutter depends on /usr/lib/x86_64-linux-gnu/libstdc++.so.6 version GLIBCXX_3.4.18 7 | sources: 8 | - ubuntu-toolchain-r-test # if we don't specify this, the libstdc++6 we get is the wrong version 9 | packages: 10 | - libstdc++6 11 | - fonts-droid 12 | before_script: 13 | - git clone https://github.com/flutter/flutter.git -b beta 14 | - ./flutter/bin/flutter doctor 15 | script: 16 | - ./flutter/bin/flutter test 17 | cache: 18 | directories: 19 | - $HOME/.pub-cache -------------------------------------------------------------------------------- /example/android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() 4 | 5 | def plugins = new Properties() 6 | def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') 7 | if (pluginsFile.exists()) { 8 | pluginsFile.withReader('UTF-8') { reader -> plugins.load(reader) } 9 | } 10 | 11 | plugins.each { name, path -> 12 | def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() 13 | include ":$name" 14 | project(":$name").projectDir = pluginDirectory 15 | } 16 | -------------------------------------------------------------------------------- /example/android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.0.1' 9 | } 10 | } 11 | 12 | allprojects { 13 | repositories { 14 | google() 15 | jcenter() 16 | } 17 | } 18 | 19 | rootProject.buildDir = '../build' 20 | subprojects { 21 | project.buildDir = "${rootProject.buildDir}/${project.name}" 22 | } 23 | subprojects { 24 | project.evaluationDependsOn(':app') 25 | } 26 | 27 | task clean(type: Delete) { 28 | delete rootProject.buildDir 29 | } 30 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: multi_image_picker 2 | description: Flutter plugin that allows you to display multi image picker on iOS and Android. 3 | version: 2.1.26 4 | author: Radoslav Vitanov 5 | homepage: https://github.com/Sh1d0w/multi_image_picker 6 | 7 | dependencies: 8 | flutter: 9 | sdk: flutter 10 | meta: ^1.1.5 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | # The following section is specific to Flutter. 17 | flutter: 18 | plugin: 19 | androidPackage: com.vitanov.multiimagepicker 20 | pluginClass: MultiImagePickerPlugin 21 | environment: 22 | sdk: ">=2.0.0-dev.28.0 <3.0.0" 23 | flutter: ">=0.1.4 <2.0.0" -------------------------------------------------------------------------------- /example/ios/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vagrant/ 3 | .sconsign.dblite 4 | .svn/ 5 | 6 | .DS_Store 7 | *.swp 8 | profile 9 | 10 | DerivedData/ 11 | build/ 12 | GeneratedPluginRegistrant.h 13 | GeneratedPluginRegistrant.m 14 | 15 | .generated/ 16 | 17 | *.pbxuser 18 | *.mode1v3 19 | *.mode2v3 20 | *.perspectivev3 21 | 22 | !default.pbxuser 23 | !default.mode1v3 24 | !default.mode2v3 25 | !default.perspectivev3 26 | 27 | xcuserdata 28 | 29 | *.moved-aside 30 | 31 | *.pyc 32 | *sync/ 33 | Icon? 34 | .tags* 35 | 36 | /Flutter/app.flx 37 | /Flutter/app.zip 38 | /Flutter/flutter_assets/ 39 | /Flutter/App.framework 40 | /Flutter/Flutter.framework 41 | /Flutter/Generated.xcconfig 42 | /ServiceDefinitions.json 43 | 44 | Pods/ 45 | .symlinks/ 46 | -------------------------------------------------------------------------------- /doc/ios.md: -------------------------------------------------------------------------------- 1 | # iOS customization 2 | 3 | You can customize different parts of the gallery picker. To do so, you can simply pass `options` param in the `pickImages` call. 4 | 5 | ```dart 6 | List resultList = await MultiImagePicker.pickImages( 7 | maxImages: 3, 8 | options: CupertinoOptions( 9 | selectionFillColor: "#ff11ab", 10 | selectionTextColor: "#ff00a5", 11 | selectionCharacter: "✓", 12 | ), 13 | ); 14 | ``` 15 | 16 | Available options are: 17 | - backgroundColor - HEX string 18 | - selectionFillColor - HEX string 19 | - selectionShadowColor - HEX string 20 | - selectionStrokeColor - HEX string 21 | - selectionTextColor - HEX string 22 | - selectionCharacter - Unicode character 23 | 24 | Text overrides will be available soon. -------------------------------------------------------------------------------- /ios/multi_image_picker.podspec: -------------------------------------------------------------------------------- 1 | # 2 | # To learn more about a Podspec see http://guides.cocoapods.org/syntax/podspec.html 3 | # 4 | Pod::Spec.new do |s| 5 | s.name = 'multi_image_picker' 6 | s.version = '2.1.22' 7 | s.summary = 'Multi image picker' 8 | s.description = <<-DESC 9 | A new flutter plugin project. 10 | DESC 11 | s.homepage = 'https://github.com/Sh1d0w/multi_image_picker' 12 | s.license = { :file => '../LICENSE' } 13 | s.author = { 'Radoslav Vitanov' => 'radoslav.vitanov@icloud.com' } 14 | s.source = { :path => '.' } 15 | s.source_files = 'Classes/**/*' 16 | s.public_header_files = 'Classes/**/*.h' 17 | s.dependency 'Flutter' 18 | s.dependency 'BSImagePicker', '~> 2.8' 19 | 20 | s.ios.deployment_target = '8.0' 21 | end 22 | 23 | -------------------------------------------------------------------------------- /example/android/app/src/main/java/com/vitanov/multiimagepickerexample/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.vitanov.multiimagepickerexample; 2 | 3 | import android.content.Intent; 4 | import android.media.MediaScannerConnection; 5 | import android.net.Uri; 6 | import android.os.Build; 7 | import android.os.Bundle; 8 | import android.os.Environment; 9 | import android.provider.MediaStore; 10 | import android.util.Log; 11 | 12 | import com.vitanov.multiimagepicker.FileDirectory; 13 | 14 | import java.io.File; 15 | 16 | import io.flutter.app.FlutterActivity; 17 | import io.flutter.plugins.GeneratedPluginRegistrant; 18 | 19 | public class MainActivity extends FlutterActivity { 20 | @Override 21 | protected void onCreate(Bundle savedInstanceState) { 22 | super.onCreate(savedInstanceState); 23 | GeneratedPluginRegistrant.registerWith(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | group 'com.vitanov.multiimagepicker' 2 | version '1.0-SNAPSHOT' 3 | 4 | buildscript { 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.0.1' 12 | } 13 | } 14 | 15 | rootProject.allprojects { 16 | repositories { 17 | google() 18 | jcenter() 19 | } 20 | } 21 | 22 | apply plugin: 'com.android.library' 23 | 24 | android { 25 | compileSdkVersion 27 26 | 27 | defaultConfig { 28 | minSdkVersion 19 29 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 30 | } 31 | lintOptions { 32 | disable 'InvalidPackage' 33 | } 34 | } 35 | 36 | dependencies { 37 | implementation 'com.zhihu.android:matisse:0.5.1' 38 | implementation 'com.github.bumptech.glide:glide:3.8.0' 39 | } 40 | -------------------------------------------------------------------------------- /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 | 8.0 25 | 26 | 27 | -------------------------------------------------------------------------------- /lib/cupertino_options.dart: -------------------------------------------------------------------------------- 1 | class CupertinoOptions { 2 | final String backgroundColor; 3 | final String selectionShadowColor; 4 | final String selectionStrokeColor; 5 | final String selectionFillColor; 6 | final String selectionTextColor; 7 | final String selectionCharacter; 8 | 9 | const CupertinoOptions({ 10 | this.backgroundColor, 11 | this.selectionFillColor, 12 | this.selectionShadowColor, 13 | this.selectionStrokeColor, 14 | this.selectionTextColor, 15 | this.selectionCharacter, 16 | }); 17 | 18 | Map toJson() { 19 | return { 20 | "backgroundColor": backgroundColor ?? "", 21 | "selectionFillColor": selectionFillColor ?? "", 22 | "selectionShadowColor": selectionShadowColor ?? "", 23 | "selectionStrokeColor": selectionStrokeColor ?? "", 24 | "selectionTextColor": selectionTextColor ?? "", 25 | "selectionCharacter": selectionCharacter ?? "", 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /example/multi_image_picker_example.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /multi_image_picker.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | 12 | ``` 13 | 14 | ``` 15 | 16 | **Expected behavior** 17 | A clear and concise description of what you expected to happen. 18 | 19 | **Screenshots** 20 | If applicable, add screenshots to help explain your problem. 21 | 22 | **Flutter (please complete the following information):** 23 | - Which version of `multi_image_picker` are you using: [e.g. 2.0.04] 24 | 25 | ``` 26 | 27 | ``` 28 | 29 | **iOS (please complete the following information):** 30 | - Device: [e.g. iPhone6] 31 | - OS: [e.g. iOS8.1] 32 | - Swift Version: [e.g. 4.0] 33 | 34 | **Android (please complete the following information):** 35 | - Device: [e.g. Nexus 5] 36 | - OS: [e.g. Lollipop 5.1] 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Radoslav Vitanov (https://github.com/Sh1d0w) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /example/lib/asset_view.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'package:multi_image_picker/asset.dart'; 3 | 4 | class AssetView extends StatefulWidget { 5 | final int _index; 6 | final Asset _asset; 7 | 8 | AssetView(this._index, this._asset); 9 | 10 | @override 11 | State createState() => AssetState(this._index, this._asset); 12 | } 13 | 14 | class AssetState extends State { 15 | int _index = 0; 16 | Asset _asset; 17 | AssetState(this._index, this._asset); 18 | 19 | @override 20 | void initState() { 21 | super.initState(); 22 | _loadImage(); 23 | } 24 | 25 | void _loadImage() async { 26 | await this._asset.requestThumbnail(300, 300); 27 | setState(() {}); 28 | } 29 | 30 | @override 31 | Widget build(BuildContext context) { 32 | if (null != this._asset.thumbData) { 33 | return Image.memory( 34 | this._asset.thumbData.buffer.asUint8List(), 35 | fit: BoxFit.cover, 36 | ); 37 | } 38 | 39 | return Text( 40 | '${this._index}', 41 | style: Theme.of(context).textTheme.headline, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /example/ios/Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - BSGridCollectionViewLayout (1.2.1) 3 | - BSImagePicker (2.8.0): 4 | - BSGridCollectionViewLayout (~> 1.2.0) 5 | - BSImageView (~> 1.0.0) 6 | - BSImageView (1.0.0) 7 | - Flutter (1.0.0) 8 | - multi_image_picker (2.1.22): 9 | - BSImagePicker (~> 2.8) 10 | - Flutter 11 | 12 | DEPENDENCIES: 13 | - Flutter (from `.symlinks/flutter/ios`) 14 | - multi_image_picker (from `.symlinks/plugins/multi_image_picker/ios`) 15 | 16 | SPEC REPOS: 17 | https://github.com/cocoapods/specs.git: 18 | - BSGridCollectionViewLayout 19 | - BSImagePicker 20 | - BSImageView 21 | 22 | EXTERNAL SOURCES: 23 | Flutter: 24 | :path: ".symlinks/flutter/ios" 25 | multi_image_picker: 26 | :path: ".symlinks/plugins/multi_image_picker/ios" 27 | 28 | SPEC CHECKSUMS: 29 | BSGridCollectionViewLayout: 1a7d6f30517d3c9d181774bc74be4dfcab7b8115 30 | BSImagePicker: b06623bd22f1941bbb04168426b95d38017e3081 31 | BSImageView: ed9c50d917111d8cac66c21c5a41c03f57908819 32 | Flutter: 9d0fac939486c9aba2809b7982dfdbb47a7b0296 33 | multi_image_picker: c94f10d464a70615cfad175d861be4cfb9511c98 34 | 35 | PODFILE CHECKSUM: ec81a62fe73c7b29f2764dd3fe8c221958ac6739 36 | 37 | COCOAPODS: 1.5.3 38 | -------------------------------------------------------------------------------- /example/multi_image_picker_example_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /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 | apply plugin: 'com.android.application' 15 | apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" 16 | 17 | android { 18 | compileSdkVersion 27 19 | 20 | lintOptions { 21 | disable 'InvalidPackage' 22 | } 23 | 24 | defaultConfig { 25 | applicationId "com.vitanov.multiimagepickerexample" 26 | minSdkVersion 19 27 | targetSdkVersion 27 28 | versionCode 1 29 | versionName "1.0" 30 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 31 | } 32 | 33 | buildTypes { 34 | release { 35 | // TODO: Add your own signing config for the release build. 36 | // Signing with the debug keys for now, so `flutter run --release` works. 37 | signingConfig signingConfigs.debug 38 | } 39 | } 40 | } 41 | 42 | flutter { 43 | source '../..' 44 | } 45 | 46 | dependencies { 47 | testImplementation 'junit:junit:4.12' 48 | androidTestImplementation 'com.android.support.test:runner:1.0.1' 49 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' 50 | } 51 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /multi_image_picker_android.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /example/ios/Runner/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | multi_image_picker_example 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UILaunchStoryboardName 26 | LaunchScreen 27 | UIMainStoryboardFile 28 | Main 29 | NSPhotoLibraryUsageDescription 30 | Allows you to choose a picture 31 | NSCameraUsageDescription 32 | Allows you to take a picture 33 | NSMicrophoneUsageDescription 34 | Allows you to record a voice 35 | UISupportedInterfaceOrientations 36 | 37 | UIInterfaceOrientationPortrait 38 | UIInterfaceOrientationLandscapeLeft 39 | UIInterfaceOrientationLandscapeRight 40 | 41 | UISupportedInterfaceOrientations~ipad 42 | 43 | UIInterfaceOrientationPortrait 44 | UIInterfaceOrientationPortraitUpsideDown 45 | UIInterfaceOrientationLandscapeLeft 46 | UIInterfaceOrientationLandscapeRight 47 | 48 | UIViewControllerBasedStatusBarAppearance 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /test/asset_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:multi_image_picker/multi_image_picker.dart'; 4 | 5 | void main() { 6 | group('Asset', () { 7 | const MethodChannel channel = MethodChannel('multi_image_picker'); 8 | 9 | final List log = []; 10 | 11 | setUp(() { 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | log.add(methodCall); 14 | return true; 15 | }); 16 | 17 | log.clear(); 18 | }); 19 | 20 | test('constructor set the identifier correctly', () { 21 | const String id = 'SOME_ID'; 22 | Asset asset = Asset(id, 100, 100); 23 | expect( 24 | asset.identifier, 25 | equals(id), 26 | ); 27 | }); 28 | 29 | test('thumbData can not have negative dimensions', () async { 30 | Asset asset = Asset('_identifier', 50, 50); 31 | 32 | expect( 33 | asset.requestThumbnail(-100, 10), 34 | throwsArgumentError, 35 | ); 36 | 37 | expect( 38 | asset.requestThumbnail(10, -100), 39 | throwsArgumentError, 40 | ); 41 | }); 42 | 43 | test('asset has correct dimensions', () async { 44 | Asset asset = Asset('_identifier', 50, 40); 45 | 46 | expect( 47 | asset.originalWidth, 48 | equals(50), 49 | ); 50 | 51 | expect( 52 | asset.originalHeight, 53 | equals(40), 54 | ); 55 | }); 56 | 57 | test('asset isLandscape and isPortrait work', () async { 58 | Asset asset1 = Asset('_identifier', 50, 40); 59 | Asset asset2 = Asset('_identifier', 40, 50); 60 | 61 | expect( 62 | asset1.isLandscape, 63 | equals(true), 64 | ); 65 | 66 | expect( 67 | asset1.isPortrait, 68 | equals(false), 69 | ); 70 | 71 | expect( 72 | asset2.isLandscape, 73 | equals(false), 74 | ); 75 | 76 | expect( 77 | asset2.isPortrait, 78 | equals(true), 79 | ); 80 | }); 81 | }); 82 | } 83 | -------------------------------------------------------------------------------- /example/pubspec.yaml: -------------------------------------------------------------------------------- 1 | name: multi_image_picker_example 2 | description: Demonstrates how to use the multi_image_picker plugin. 3 | 4 | dependencies: 5 | flutter: 6 | sdk: flutter 7 | 8 | # The following adds the Cupertino Icons font to your application. 9 | # Use with the CupertinoIcons class for iOS style icons. 10 | cupertino_icons: ^0.1.2 11 | 12 | dev_dependencies: 13 | flutter_test: 14 | sdk: flutter 15 | 16 | multi_image_picker: 17 | path: ../ 18 | 19 | # For information on the generic Dart part of this file, see the 20 | # following page: https://www.dartlang.org/tools/pub/pubspec 21 | 22 | # The following section is specific to Flutter. 23 | flutter: 24 | 25 | # The following line ensures that the Material Icons font is 26 | # included with your application, so that you can use the icons in 27 | # the material Icons class. 28 | uses-material-design: true 29 | 30 | # To add assets to your application, add an assets section, like this: 31 | # assets: 32 | # - images/a_dot_burr.jpeg 33 | # - images/a_dot_ham.jpeg 34 | 35 | # An image asset can refer to one or more resolution-specific "variants", see 36 | # https://flutter.io/assets-and-images/#resolution-aware. 37 | 38 | # For details regarding adding assets from package dependencies, see 39 | # https://flutter.io/assets-and-images/#from-packages 40 | 41 | # To add custom fonts to your application, add a fonts section here, 42 | # in this "flutter" section. Each entry in this list should have a 43 | # "family" key with the font family name, and a "fonts" key with a 44 | # list giving the asset and other descriptors for the font. For 45 | # example: 46 | # fonts: 47 | # - family: Schyler 48 | # fonts: 49 | # - asset: fonts/Schyler-Regular.ttf 50 | # - asset: fonts/Schyler-Italic.ttf 51 | # style: italic 52 | # - family: Trajan Pro 53 | # fonts: 54 | # - asset: fonts/TrajanPro.ttf 55 | # - asset: fonts/TrajanPro_Bold.ttf 56 | # weight: 700 57 | # 58 | # For details regarding fonts from package dependencies, 59 | # see https://flutter.io/custom-fonts/#from-packages 60 | -------------------------------------------------------------------------------- /example/lib/main.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/material.dart'; 2 | import 'dart:async'; 3 | 4 | import 'package:flutter/services.dart'; 5 | import 'package:multi_image_picker/asset.dart'; 6 | import 'package:multi_image_picker/multi_image_picker.dart'; 7 | import 'asset_view.dart'; 8 | 9 | void main() => runApp(new MyApp()); 10 | 11 | class MyApp extends StatefulWidget { 12 | @override 13 | _MyAppState createState() => new _MyAppState(); 14 | } 15 | 16 | class _MyAppState extends State { 17 | List images = List(); 18 | String _error; 19 | 20 | @override 21 | void initState() { 22 | super.initState(); 23 | } 24 | 25 | Widget buildGridView() { 26 | return GridView.count( 27 | crossAxisCount: 3, 28 | children: List.generate(images.length, (index) { 29 | return AssetView(index, images[index]); 30 | }), 31 | ); 32 | } 33 | 34 | Future loadAssets() async { 35 | setState(() { 36 | images = List(); 37 | }); 38 | 39 | List resultList; 40 | String error; 41 | 42 | try { 43 | resultList = await MultiImagePicker.pickImages( 44 | maxImages: 300, 45 | ); 46 | } on PlatformException catch (e) { 47 | error = e.message; 48 | } 49 | 50 | // If the widget was removed from the tree while the asynchronous platform 51 | // message was in flight, we want to discard the reply rather than calling 52 | // setState to update our non-existent appearance. 53 | if (!mounted) return; 54 | 55 | setState(() { 56 | images = resultList; 57 | if (error == null) _error = 'No Error Dectected'; 58 | }); 59 | } 60 | 61 | @override 62 | Widget build(BuildContext context) { 63 | return new MaterialApp( 64 | home: new Scaffold( 65 | appBar: new AppBar( 66 | title: const Text('Plugin example app'), 67 | ), 68 | body: Column( 69 | children: [ 70 | Center(child: Text('Error: $_error')), 71 | RaisedButton( 72 | child: Text("Pick images"), 73 | onPressed: loadAssets, 74 | ), 75 | Expanded( 76 | child: buildGridView(), 77 | ) 78 | ], 79 | ), 80 | ), 81 | ); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /example/android/app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 17 | 21 | 28 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /example/ios/Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :ios, '10.0' 3 | 4 | # CocoaPods analytics sends network stats synchronously affecting flutter build latency. 5 | ENV['COCOAPODS_DISABLE_STATS'] = 'true' 6 | 7 | def parse_KV_file(file, separator='=') 8 | file_abs_path = File.expand_path(file) 9 | if !File.exists? file_abs_path 10 | return []; 11 | end 12 | pods_ary = [] 13 | skip_line_start_symbols = ["#", "/"] 14 | File.foreach(file_abs_path) { |line| 15 | next if skip_line_start_symbols.any? { |symbol| line =~ /^\s*#{symbol}/ } 16 | plugin = line.split(pattern=separator) 17 | if plugin.length == 2 18 | podname = plugin[0].strip() 19 | path = plugin[1].strip() 20 | podpath = File.expand_path("#{path}", file_abs_path) 21 | pods_ary.push({:name => podname, :path => podpath}); 22 | else 23 | puts "Invalid plugin specification: #{line}" 24 | end 25 | } 26 | return pods_ary 27 | end 28 | 29 | target 'Runner' do 30 | use_frameworks! 31 | 32 | # Prepare symlinks folder. We use symlinks to avoid having Podfile.lock 33 | # referring to absolute paths on developers' machines. 34 | system('rm -rf .symlinks') 35 | system('mkdir -p .symlinks/plugins') 36 | 37 | # Flutter Pods 38 | generated_xcode_build_settings = parse_KV_file('./Flutter/Generated.xcconfig') 39 | if generated_xcode_build_settings.empty? 40 | puts "Generated.xcconfig must exist. If you're running pod install manually, make sure flutter packages get is executed first." 41 | end 42 | generated_xcode_build_settings.map { |p| 43 | if p[:name] == 'FLUTTER_FRAMEWORK_DIR' 44 | symlink = File.join('.symlinks', 'flutter') 45 | File.symlink(File.dirname(p[:path]), symlink) 46 | pod 'Flutter', :path => File.join(symlink, File.basename(p[:path])) 47 | end 48 | } 49 | 50 | # Plugin Pods 51 | plugin_pods = parse_KV_file('../.flutter-plugins') 52 | plugin_pods.map { |p| 53 | symlink = File.join('.symlinks', 'plugins', p[:name]) 54 | File.symlink(p[:path], symlink) 55 | pod p[:name], :path => File.join(symlink, 'ios') 56 | } 57 | end 58 | 59 | post_install do |installer| 60 | installer.pods_project.targets.each do |target| 61 | target.build_configurations.each do |config| 62 | config.build_settings['ENABLE_BITCODE'] = 'NO' 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /example/android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # multi_image_picker 2 | 3 | [![build](https://img.shields.io/travis/Sh1d0w/multi_image_picker.svg)](https://pub.dartlang.org/packages/multi_image_picker) 4 | [![pub package](https://img.shields.io/pub/v/multi_image_picker.svg)](https://pub.dartlang.org/packages/multi_image_picker) 5 | 6 | > Flutter plugin that allows you to display multi image picker on iOS and Android. 7 | 8 | ## iOS 9 | 10 | ![Screenshot iOS 1](screenshots/ios-1.png) ![Screenshot iOS 2](screenshots/ios-2.png) ![Screenshot iOS 3](screenshots/ios-3.png) 11 | 12 | For the iOS picker the plugin uses [BSImagePicker](https://github.com/mikaoj/BSImagePicker) 13 | 14 | ## Android 15 | 16 | ![Screenshot Anroid 1](screenshots/android-1.png) ![Screenshot Anroid 2](screenshots/android-2.png) ![Screenshot Anroid 3](screenshots/android-3.png) 17 | 18 | For the Android picker the plugin uses [Matisse](https://github.com/zhihu/Matisse) 19 | 20 | ## Usage 21 | 22 | First you need to [add](https://pub.dartlang.org/packages/multi_image_picker#-installing-tab-) the plugin to your project. 23 | 24 | ### iOS 25 | 26 | You need to add those strings to your Info.plist file in order the plugin to work: 27 | ```xml 28 | NSPhotoLibraryUsageDescription 29 | Example usage description 30 | NSCameraUsageDescription 31 | Example usage description 32 | ``` 33 | 34 | ### Android 35 | 36 | You need to request those permissions in AndroidManifest.xml in order the plugin to work: 37 | 38 | ```xml 39 | 40 | 41 | 42 | ``` 43 | 44 | For example code usage, please see [here](https://github.com/Sh1d0w/multi_image_picker/blob/master/example/lib/main.dart) 45 | 46 | ## Theming and localization 47 | 48 | You can customize different parts of the gallery picker. For reference see below the available options for the different platforms: 49 | 50 | Customization on [Android](https://github.com/Sh1d0w/multi_image_picker/tree/master/doc/android.md) 51 | 52 | Customization on [iOS](https://github.com/Sh1d0w/multi_image_picker/tree/master/doc/ios.md) 53 | 54 | ## API 55 | 56 | [MultiImagePicker](https://pub.dartlang.org/documentation/multi_image_picker/latest/picker/MultiImagePicker-class.html) 57 | 58 | [Asset](https://pub.dartlang.org/documentation/multi_image_picker/latest/asset/Asset-class.html) 59 | 60 | ## TODO 61 | 62 | - [x] Add support for more plugin specific options for iOS and Android, that allows more customization 63 | - [x] Improve docs 64 | - [ ] Allow choosing videos 65 | 66 | ## Related 67 | 68 | - [image_picker](https://pub.dartlang.org/packages/image_picker) - Official Flutter image picker plugin 69 | 70 | ## License 71 | 72 | MIT © [Radoslav Vitanov](https://github.com/Sh1d0w) -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/) 5 | and this project adheres to [Semantic Versioning](http://semver.org/). 6 | 7 | ### Changed (v2.1.23) 8 | 9 | ## 2018-08-31 10 | ### Added 11 | - Improved the docs 12 | 13 | ### Changed (v2.1.22) 14 | 15 | ## 2018-08-28 16 | ### Added 17 | - Add originalWidth, originalHeight, isPortrait and isLandscape getters for the Asset class 18 | 19 | ### Changed (v2.1.21) 20 | 21 | ## 2018-08-24 22 | ### Added 23 | - Add release(), releaseOriginal() and releaseThumb() methods to help clean up the image data when it is no longer needed 24 | 25 | ### Changed (v2.1.02) 26 | 27 | ## 2018-08-20 28 | ### Fix 29 | - Fix null pointer exception on Android when finishing from another activity (thanks to xia-weiyang) 30 | 31 | ### Changed (v2.1.01) 32 | 33 | ## 2018-08-16 34 | ### Change 35 | - Add getters to Asset class 36 | 37 | ### Changed (v2.1.00) 38 | 39 | ## 2018-08-16 40 | ### BREAKING CHANGE 41 | - Asset's `requestThumbnail` and `requestOriginal` methods now will return Future. Removed the method callbacks. 42 | 43 | ### Changed (v2.0.04) 44 | 45 | ## 2018-08-16 46 | ### Fixed 47 | - Correctly crop the thumb on iOS 48 | 49 | ### Changed (v2.0.03) 50 | 51 | ## 2018-08-16 52 | ### Added 53 | - Allow network access to download images present only in iCloud 54 | 55 | ### Changed (v2.0.02) 56 | 57 | ## 2018-08-16 58 | ### Fixed 59 | - Improve thumbs quality on iOS to always deliver best of it 60 | 61 | ### Changed (v2.0.01) 62 | 63 | ## 2018-08-16 64 | ### Fixed 65 | - Fix picking original image on Android was not triggering properly the callback 66 | 67 | ### Changed (v2.0.0) 68 | 69 | ## 2018-08-15 70 | ### BREAKING CHANGE 71 | - The plugin have been redesigned to be more responsive and flexible. 72 | - pickImages method will no longer return List, instead it will return List 73 | - You can then request asset thumbnails or the original image, which will load asyncrhoniously without blocking the main UI thred. For more info see the examples directory. 74 | 75 | ### Added 76 | - `Asset` class, with methods `requestThumbnail(int width, int height, callback)` and `requestOriginal(callback)` 77 | 78 | ### Changed (v1.0.53) 79 | 80 | ## 2018-08-13 81 | ### Fixed 82 | - Fix crash on iOS when picking a lot of images. 83 | 84 | ### Changed (v1.0.52) 85 | 86 | ## 2018-08-12 87 | ### Fixed 88 | - Picking images on iOS now will properly handle PHAssets 89 | 90 | ### Changed (v1.0.51) 91 | 92 | ## 2018-08-07 93 | ### Changed 94 | - Fix a crash on Android caused by closing and reopening the gallery 95 | 96 | ### Changed (v1.0.5) 97 | 98 | ## 2018-08-07 99 | ### Add 100 | - Support iOS and Android customizations 101 | 102 | ### Changed (v1.0.4) 103 | 104 | ## 2018-08-06 105 | ### Changed 106 | - iOS: Add missing super.init() call in the class constructor 107 | 108 | ### Changed (v1.0.3) 109 | 110 | ## 2018-08-05 111 | ### Changed 112 | - Changed sdk: ">=2.0.0-dev.28.0 <3.0.0" 113 | 114 | ### Changed (v1.0.2) 115 | 116 | ## 2018-08-05 117 | ### Added 118 | - Add Support for Dart 2 in pubspec.yaml file 119 | 120 | ### Changed (v1.0.1) 121 | 122 | ## 2018-08-05 123 | ### Added 124 | - Initial release with basic support for iOS and Android -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 31 | 32 | 33 | 34 | 40 | 41 | 42 | 43 | 44 | 45 | 56 | 58 | 64 | 65 | 66 | 67 | 68 | 69 | 75 | 77 | 83 | 84 | 85 | 86 | 88 | 89 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /test/multi_image_picker_test.dart: -------------------------------------------------------------------------------- 1 | import 'package:flutter/services.dart'; 2 | import 'package:flutter_test/flutter_test.dart'; 3 | import 'package:multi_image_picker/multi_image_picker.dart'; 4 | 5 | void main() { 6 | group('MultiImagePicker', () { 7 | const MethodChannel channel = MethodChannel('multi_image_picker'); 8 | 9 | final List log = []; 10 | 11 | setUp(() { 12 | channel.setMockMethodCallHandler((MethodCall methodCall) async { 13 | log.add(methodCall); 14 | if (methodCall.method == 'requestOriginal' || 15 | methodCall.method == 'requestThumbnail') { 16 | return true; 17 | } 18 | return [ 19 | {'identifier': 'SOME_ID_1'}, 20 | {'identifier': 'SOME_ID_2'} 21 | ]; 22 | }); 23 | 24 | log.clear(); 25 | }); 26 | 27 | group('#pickImages', () { 28 | test('passes max images argument correctly', () async { 29 | await MultiImagePicker.pickImages(maxImages: 5); 30 | 31 | expect( 32 | log, 33 | [ 34 | isMethodCall('pickImages', arguments: { 35 | 'maxImages': 5, 36 | 'iosOptions': CupertinoOptions().toJson(), 37 | }), 38 | ], 39 | ); 40 | }); 41 | 42 | test('passes cuppertino options argument correctly', () async { 43 | CupertinoOptions options = CupertinoOptions( 44 | backgroundColor: '#ffde05', 45 | selectionCharacter: 'A', 46 | selectionFillColor: '#004ed5', 47 | selectionShadowColor: '#05e43d', 48 | selectionStrokeColor: '#0f5e4D', 49 | selectionTextColor: '#ffffff', 50 | ); 51 | await MultiImagePicker.pickImages(maxImages: 5, options: options); 52 | 53 | expect( 54 | log, 55 | [ 56 | isMethodCall('pickImages', arguments: { 57 | 'maxImages': 5, 58 | 'iosOptions': options.toJson(), 59 | }), 60 | ], 61 | ); 62 | }); 63 | 64 | test('does not accept a negative images count', () { 65 | expect( 66 | MultiImagePicker.pickImages(maxImages: -10), 67 | throwsArgumentError, 68 | ); 69 | }); 70 | }); 71 | 72 | test('requestOriginal accepts correct params', () async { 73 | const String id = 'SOME_ID'; 74 | await MultiImagePicker.requestOriginal(id); 75 | 76 | expect( 77 | log, 78 | [ 79 | isMethodCall('requestOriginal', arguments: { 80 | 'identifier': id, 81 | }), 82 | ], 83 | ); 84 | }); 85 | 86 | group('#requestThumbnail', () { 87 | const String id = 'SOME_ID'; 88 | const int width = 100; 89 | const int height = 200; 90 | test('accepts correct params', () async { 91 | await MultiImagePicker.requestThumbnail(id, width, height); 92 | 93 | expect( 94 | log, 95 | [ 96 | isMethodCall('requestThumbnail', arguments: { 97 | 'identifier': id, 98 | 'width': width, 99 | 'height': height, 100 | }), 101 | ], 102 | ); 103 | }); 104 | 105 | test('does not accept a negative width or height', () { 106 | expect( 107 | MultiImagePicker.requestThumbnail(id, -100, height), 108 | throwsArgumentError, 109 | ); 110 | 111 | expect( 112 | MultiImagePicker.requestThumbnail(id, width, -100), 113 | throwsArgumentError, 114 | ); 115 | }); 116 | }); 117 | }); 118 | } 119 | -------------------------------------------------------------------------------- /lib/picker.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:meta/meta.dart'; 5 | import 'package:multi_image_picker/asset.dart'; 6 | import 'package:multi_image_picker/cupertino_options.dart'; 7 | 8 | class MultiImagePicker { 9 | static const MethodChannel _channel = 10 | const MethodChannel('multi_image_picker'); 11 | 12 | /// Invokes the multi image picker selector. 13 | /// 14 | /// You must provide [maxImages] option, which will limit 15 | /// the number of images that the user can choose. On iOS 16 | /// you can pass also [options] parameter which should be 17 | /// an instance of [CupertinoOptions] class. It allows you 18 | /// to customize the look of the image picker. On android 19 | /// you have to provide custom styles via resource files 20 | /// as specified in the official docs on Github. 21 | /// 22 | /// This method returns list of [Asset] objects. Because 23 | /// they are just placeholders containing the actual 24 | /// identifier to the image, not the image itself you can 25 | /// pick thousands of images at a time, with no performance 26 | /// penalty. How to request the original image or a thumb 27 | /// you can refer to the docs for the Asset class. 28 | static Future> pickImages({ 29 | @required int maxImages, 30 | CupertinoOptions options = const CupertinoOptions(), 31 | }) async { 32 | assert(maxImages != null); 33 | 34 | if (maxImages != null && maxImages < 0) { 35 | throw new ArgumentError.value(maxImages, 'maxImages cannot be negative'); 36 | } 37 | 38 | final List images = 39 | await _channel.invokeMethod('pickImages', { 40 | 'maxImages': maxImages, 41 | 'iosOptions': options.toJson(), 42 | }); 43 | 44 | var assets = List(); 45 | for (var item in images) { 46 | var asset = Asset( 47 | item['identifier'], 48 | item['width'], 49 | item['height'], 50 | path : item["filePath"], 51 | ); 52 | assets.add(asset); 53 | } 54 | return assets; 55 | } 56 | 57 | /// Requests a thumbnail with [width] and [height] 58 | /// for a given [identifier]. 59 | /// 60 | /// This method is used by the asset class, you 61 | /// should not invoke it manually. For more info 62 | /// refer to [Asset] class docs. 63 | /// 64 | /// The actual image data is sent via BinaryChannel. 65 | static Future requestThumbnail( 66 | String identifier, int width, int height) async { 67 | assert(identifier != null); 68 | assert(width != null); 69 | assert(height != null); 70 | 71 | if (width != null && width < 0) { 72 | throw new ArgumentError.value(width, 'width cannot be negative'); 73 | } 74 | 75 | if (height != null && height < 0) { 76 | throw new ArgumentError.value(height, 'height cannot be negative'); 77 | } 78 | 79 | bool ret = 80 | await _channel.invokeMethod("requestThumbnail", { 81 | "identifier": identifier, 82 | "width": width, 83 | "height": height, 84 | }); 85 | return ret; 86 | } 87 | 88 | /// Requests the original image data for a given 89 | /// [identifier]. 90 | /// 91 | /// This method is used by the asset class, you 92 | /// should not invoke it manually. For more info 93 | /// refer to [Asset] class docs. 94 | /// 95 | /// The actual image data is sent via BinaryChannel. 96 | static Future requestOriginal(String identifier) async { 97 | bool ret = await _channel.invokeMethod("requestOriginal", { 98 | "identifier": identifier, 99 | }); 100 | return ret; 101 | } 102 | 103 | /// Refresh image gallery with specific path 104 | /// [path]. 105 | /// 106 | /// This method is used by refresh image gallery 107 | /// Some of the image picker would not be refresh automatically 108 | /// You can refresh it manually. 109 | static Future refreshImage({@required String path,}) async { 110 | assert(path != null); 111 | bool result = await _channel.invokeMethod("refreshImage", { 112 | "path" : path 113 | }); 114 | 115 | return result; 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /lib/asset.dart: -------------------------------------------------------------------------------- 1 | import 'dart:async'; 2 | 3 | import 'package:flutter/services.dart'; 4 | import 'package:multi_image_picker/picker.dart'; 5 | 6 | class Asset { 7 | /// The resource identifier 8 | String _identifier; 9 | 10 | /// The resource real path 11 | String path; 12 | 13 | /// Original image width 14 | int _originalWidth; 15 | 16 | /// Original image height 17 | int _originalHeight; 18 | 19 | /// Holds the thumb binary data after it is requested 20 | ByteData _thumbData; 21 | 22 | /// Holds the original image data after it is requested 23 | ByteData _imageData; 24 | 25 | Asset( 26 | this._identifier, 27 | this._originalWidth, 28 | this._originalHeight, { 29 | this.path, 30 | }); 31 | 32 | /// The BinaryChannel name this asset is listening on. 33 | String get _channel { 34 | return 'multi_image_picker/image/$_identifier'; 35 | } 36 | 37 | /// Returns the thumb data if it was loaded 38 | ByteData get thumbData { 39 | return _thumbData; 40 | } 41 | 42 | /// Returns the original image width 43 | int get originalWidth { 44 | return _originalWidth; 45 | } 46 | 47 | /// Returns the original image height 48 | int get originalHeight { 49 | return _originalHeight; 50 | } 51 | 52 | /// Returns true if the image is landscape 53 | bool get isLandscape { 54 | return _originalWidth > _originalHeight; 55 | } 56 | 57 | /// Returns true if the image is Portrait 58 | bool get isPortrait { 59 | return _originalWidth < _originalHeight; 60 | } 61 | 62 | /// Returns the original image data 63 | ByteData get imageData { 64 | return _imageData; 65 | } 66 | 67 | /// Returns the image identifier 68 | String get identifier { 69 | return _identifier; 70 | } 71 | 72 | /// Returns the real image path 73 | String get filePath => path; 74 | 75 | /// Releases the thumb data. 76 | /// 77 | /// You should consider cleaning up the thumb data 78 | /// once you don't need it in order to free some 79 | /// memory. 80 | void releaseThumb() { 81 | _thumbData = null; 82 | } 83 | 84 | /// Releases the original image data. 85 | /// 86 | /// You should consider cleaning up the original data 87 | /// once you don't need it in order to free some 88 | /// memory. 89 | void releaseOriginal() { 90 | _imageData = null; 91 | } 92 | 93 | /// Releases both the thumb and original image data. 94 | /// 95 | /// You should consider cleaning up the data 96 | /// once you don't need it in order to free some 97 | /// memory. 98 | void release() { 99 | releaseThumb(); 100 | releaseOriginal(); 101 | } 102 | 103 | /// Requests a thumbnail for the [Asset] with give [width] and [hegiht]. 104 | /// 105 | /// The method returns a Future with the [ByteData] for the thumb, 106 | /// as well as storing it in the _thumbData property which can be requested 107 | /// later again, without need to call this method again. 108 | /// 109 | /// Once you don't need this thumb data it is a good practice to release it, 110 | /// by calling releaseThumb() method. 111 | Future requestThumbnail(int width, int height) async { 112 | assert(width != null); 113 | assert(height != null); 114 | 115 | if (width != null && width < 0) { 116 | throw new ArgumentError.value(width, 'width cannot be negative'); 117 | } 118 | 119 | if (height != null && height < 0) { 120 | throw new ArgumentError.value(height, 'height cannot be negative'); 121 | } 122 | 123 | Completer completer = new Completer(); 124 | BinaryMessages.setMessageHandler(_channel, (ByteData message) { 125 | _thumbData = message; 126 | completer.complete(message); 127 | BinaryMessages.setMessageHandler(_channel, null); 128 | }); 129 | 130 | MultiImagePicker.requestThumbnail(_identifier, width, height); 131 | return completer.future; 132 | } 133 | 134 | /// Requests the original image for that asset. 135 | /// 136 | /// The method returns a Future with the [ByteData] for the image, 137 | /// as well as storing it in the _imageData property which can be requested 138 | /// later again, without need to call this method again. 139 | /// 140 | /// Once you don't need this data it is a good practice to release it, 141 | /// by calling releaseOriginal() method. 142 | Future requestOriginal() { 143 | Completer completer = new Completer(); 144 | BinaryMessages.setMessageHandler(_channel, (ByteData message) { 145 | _imageData = message; 146 | completer.complete(message); 147 | BinaryMessages.setMessageHandler(_channel, null); 148 | }); 149 | 150 | MultiImagePicker.requestOriginal(_identifier); 151 | return completer.future; 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /doc/android.md: -------------------------------------------------------------------------------- 1 | # Android customization 2 | 3 | The library used in this project only allows customization via xml styles, so the only way to provide custom styles for the gallery picker is to override the default strings via resource files (this can not be overidden in through Flutter package interface). 4 | 5 | This guide aims to help you do that. 6 | 7 | Here is the default theme that the plugin uses: 8 | 9 | ```xml 10 | 32 | ``` 33 | 34 | If you want you can override certain parts of the theme, or all aspects of it. 35 | 36 | To do so in your Flutter project navigate to `android/app/src/main/res/values` folder. Open `styles.xml` file. It's contents should default to: 37 | 38 | ```xml 39 | 40 | 41 | 46 | 47 | ``` 48 | 49 | Then you can override any of the defined colors in the default gallery theme. For example if you want to change the primary color to red add: 50 | 51 | ```xml 52 | #EF021A 53 | ``` 54 | 55 | before the `` closing tag. The end result should look like this: 56 | 57 | ```xml 58 | 59 | 60 | 65 | 68 | #EF021A 69 | 70 | ``` 71 | 72 | The same way you can override any strings that gallery uses. Just create a new file called `strings.xml` in the same folder as `styles.xml` and add this content. 73 | 74 | ```xml 75 | 76 | 79 | All Media 80 | 81 | Preview 82 | Apply 83 | Apply(%1$d) 84 | Back 85 | Camera 86 | No media yet 87 | OK 88 | 89 | You have reached max selectable 90 | You can only select up to %1$d media files 91 | Under quality 92 | Over quality 93 | Unsupported file type 94 | Can\'t select images and videos at the same time 95 | No App found supporting video preview 96 | Can\'t select the images larger than %1$d MB 97 | %1$d images over %2$d MB. Original will be unchecked 98 | Original 99 | Sure 100 | Sure(%1$d) 101 | 102 | ``` 103 | 104 | You can the replace any texts you want. -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /example/android/gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /android/src/main/java/com/vitanov/multiimagepicker/FileDirectory.java: -------------------------------------------------------------------------------- 1 | package com.vitanov.multiimagepicker; 2 | 3 | import android.content.ContentUris; 4 | import android.content.Context; 5 | import android.database.Cursor; 6 | import android.net.Uri; 7 | import android.os.Build; 8 | import android.os.Environment; 9 | import android.provider.DocumentsContract; 10 | import android.provider.MediaStore; 11 | 12 | 13 | public class FileDirectory { 14 | 15 | /** 16 | * Get a file path from a Uri. This will get the the path for Storage Access 17 | * Framework Documents, as well as the _data field for the MediaStore and 18 | * other file-based ContentProviders. 19 | * 20 | * @param context The context. 21 | * @param uri The Uri to query. 22 | * @author paulburke 23 | */ 24 | public static String getPath(final Context context, final Uri uri) { 25 | 26 | final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT; 27 | 28 | // DocumentProvider 29 | if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { 30 | // ExternalStorageProvider 31 | if (isExternalStorageDocument(uri)) { 32 | final String docId = DocumentsContract.getDocumentId(uri); 33 | final String[] split = docId.split(":"); 34 | final String type = split[0]; 35 | 36 | if ("primary".equalsIgnoreCase(type)) { 37 | return Environment.getExternalStorageDirectory() + "/" + split[1]; 38 | } 39 | 40 | // TODO handle non-primary volumes 41 | } 42 | // DownloadsProvider 43 | else if (isDownloadsDocument(uri)) { 44 | 45 | final String id = DocumentsContract.getDocumentId(uri); 46 | final Uri contentUri = ContentUris.withAppendedId( 47 | Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 48 | 49 | return getDataColumn(context, contentUri, null, null); 50 | } 51 | // MediaProvider 52 | else if (isMediaDocument(uri)) { 53 | final String docId = DocumentsContract.getDocumentId(uri); 54 | final String[] split = docId.split(":"); 55 | final String type = split[0]; 56 | 57 | Uri contentUri = null; 58 | if ("image".equals(type)) { 59 | contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; 60 | } else if ("video".equals(type)) { 61 | contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; 62 | } else if ("audio".equals(type)) { 63 | contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI; 64 | } 65 | 66 | final String selection = "_id=?"; 67 | final String[] selectionArgs = new String[] { 68 | split[1] 69 | }; 70 | 71 | return getDataColumn(context, contentUri, selection, selectionArgs); 72 | } 73 | } 74 | // MediaStore (and general) 75 | else if ("content".equalsIgnoreCase(uri.getScheme())) { 76 | return getDataColumn(context, uri, null, null); 77 | } 78 | // File 79 | else if ("file".equalsIgnoreCase(uri.getScheme())) { 80 | return uri.getPath(); 81 | } 82 | 83 | return null; 84 | } 85 | 86 | /** 87 | * Get the value of the data column for this Uri. This is useful for 88 | * MediaStore Uris, and other file-based ContentProviders. 89 | * 90 | * @param context The context. 91 | * @param uri The Uri to query. 92 | * @param selection (Optional) Filter used in the query. 93 | * @param selectionArgs (Optional) Selection arguments used in the query. 94 | * @return The value of the _data column, which is typically a file path. 95 | */ 96 | public static String getDataColumn(Context context, Uri uri, String selection, 97 | String[] selectionArgs) { 98 | 99 | Cursor cursor = null; 100 | final String column = "_data"; 101 | final String[] projection = { 102 | column 103 | }; 104 | 105 | try { 106 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, 107 | null); 108 | if (cursor != null && cursor.moveToFirst()) { 109 | final int column_index = cursor.getColumnIndexOrThrow(column); 110 | return cursor.getString(column_index); 111 | } 112 | } finally { 113 | if (cursor != null) 114 | cursor.close(); 115 | } 116 | return null; 117 | } 118 | 119 | 120 | /** 121 | * @param uri The Uri to check. 122 | * @return Whether the Uri authority is ExternalStorageProvider. 123 | */ 124 | public static boolean isExternalStorageDocument(Uri uri) { 125 | return "com.android.externalstorage.documents".equals(uri.getAuthority()); 126 | } 127 | 128 | /** 129 | * @param uri The Uri to check. 130 | * @return Whether the Uri authority is DownloadsProvider. 131 | */ 132 | public static boolean isDownloadsDocument(Uri uri) { 133 | return "com.android.providers.downloads.documents".equals(uri.getAuthority()); 134 | } 135 | 136 | /** 137 | * @param uri The Uri to check. 138 | * @return Whether the Uri authority is MediaProvider. 139 | */ 140 | public static boolean isMediaDocument(Uri uri) { 141 | return "com.android.providers.media.documents".equals(uri.getAuthority()); 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /ios/Classes/SwiftMultiImagePickerPlugin.swift: -------------------------------------------------------------------------------- 1 | import Flutter 2 | import UIKit 3 | import Photos 4 | import BSImagePicker 5 | 6 | public class SwiftMultiImagePickerPlugin: NSObject, FlutterPlugin { 7 | var controller: FlutterViewController! 8 | var imagesResult: FlutterResult? 9 | var messenger: FlutterBinaryMessenger; 10 | 11 | let genericError = "500" 12 | 13 | init(cont: FlutterViewController, messenger: FlutterBinaryMessenger) { 14 | self.controller = cont; 15 | self.messenger = messenger; 16 | super.init(); 17 | } 18 | 19 | public static func register(with registrar: FlutterPluginRegistrar) { 20 | let channel = FlutterMethodChannel(name: "multi_image_picker", binaryMessenger: registrar.messenger()) 21 | 22 | let app = UIApplication.shared 23 | let controller : FlutterViewController = app.delegate!.window!!.rootViewController as! FlutterViewController; 24 | let instance = SwiftMultiImagePickerPlugin.init(cont: controller, messenger: registrar.messenger()) 25 | registrar.addMethodCallDelegate(instance, channel: channel) 26 | } 27 | 28 | public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { 29 | switch (call.method) { 30 | case "pickImages": 31 | let vc = BSImagePickerViewController() 32 | let arguments = call.arguments as! Dictionary 33 | let maxImages = arguments["maxImages"] as! Int 34 | let options = arguments["iosOptions"] as! Dictionary 35 | vc.maxNumberOfSelections = maxImages 36 | 37 | if let backgroundColor = options["backgroundColor"] { 38 | if (!backgroundColor.isEmpty) { 39 | vc.backgroundColor = hexStringToUIColor(hex: backgroundColor) 40 | } 41 | } 42 | 43 | if let selectionFillColor = options["selectionFillColor"] { 44 | if (!selectionFillColor.isEmpty) { 45 | vc.selectionFillColor = hexStringToUIColor(hex: selectionFillColor) 46 | } 47 | } 48 | 49 | if let selectionShadowColor = options["selectionShadowColor"] { 50 | if (!selectionShadowColor.isEmpty) { 51 | vc.selectionShadowColor = hexStringToUIColor(hex: selectionShadowColor) 52 | } 53 | } 54 | 55 | if let selectionStrokeColor = options["selectionStrokeColor"] { 56 | if (!selectionStrokeColor.isEmpty) { 57 | vc.selectionStrokeColor = hexStringToUIColor(hex: selectionStrokeColor) 58 | } 59 | } 60 | 61 | if let selectionTextColor = options["selectionTextColor"] { 62 | if (!selectionTextColor.isEmpty) { 63 | vc.selectionTextAttributes[NSAttributedStringKey.foregroundColor] = hexStringToUIColor(hex: selectionTextColor) 64 | } 65 | } 66 | 67 | if let selectionCharacter = options["selectionCharacter"] { 68 | if (!selectionCharacter.isEmpty) { 69 | vc.selectionCharacter = Character(selectionCharacter) 70 | } 71 | } 72 | 73 | controller!.bs_presentImagePickerController(vc, animated: true, 74 | select: { (asset: PHAsset) -> Void in 75 | 76 | }, deselect: { (asset: PHAsset) -> Void in 77 | 78 | }, cancel: { (assets: [PHAsset]) -> Void in 79 | result([]) 80 | }, finish: { (assets: [PHAsset]) -> Void in 81 | var results = [NSDictionary](); 82 | for asset in assets { 83 | results.append([ 84 | "identifier": asset.localIdentifier, 85 | "width": asset.pixelWidth, 86 | "height": asset.pixelHeight, 87 | "filePath" : "", 88 | ]); 89 | } 90 | result(results); 91 | }, completion: nil) 92 | case "requestThumbnail": 93 | let arguments = call.arguments as! Dictionary 94 | let identifier = arguments["identifier"] as! String 95 | let width = arguments["width"] as! Int 96 | let height = arguments["height"] as! Int 97 | 98 | let manager = PHImageManager.default() 99 | let options = PHImageRequestOptions() 100 | 101 | options.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat 102 | options.resizeMode = PHImageRequestOptionsResizeMode.exact 103 | options.isSynchronous = false 104 | options.isNetworkAccessAllowed = true 105 | 106 | let assets: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil) 107 | 108 | if (assets.count > 0) { 109 | let asset: PHAsset = assets[0]; 110 | 111 | let ID: PHImageRequestID = manager.requestImage( 112 | for: asset, 113 | targetSize: CGSize(width: width, height: height), 114 | contentMode: PHImageContentMode.aspectFill, 115 | options: options, 116 | resultHandler: { 117 | (image: UIImage?, info) in 118 | self.messenger.send(onChannel: "multi_image_picker/image/" + identifier, message: UIImageJPEGRepresentation(image!, 1.0)) 119 | }) 120 | 121 | if(PHInvalidImageRequestID != ID) { 122 | result(true); 123 | } 124 | } 125 | case "requestOriginal": 126 | let arguments = call.arguments as! Dictionary 127 | let identifier = arguments["identifier"] as! String 128 | let manager = PHImageManager.default() 129 | let options = PHImageRequestOptions() 130 | 131 | options.deliveryMode = PHImageRequestOptionsDeliveryMode.highQualityFormat 132 | options.isSynchronous = false 133 | options.isNetworkAccessAllowed = true 134 | 135 | let assets: PHFetchResult = PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil) 136 | 137 | if (assets.count > 0) { 138 | let asset: PHAsset = assets[0]; 139 | 140 | let ID: PHImageRequestID = manager.requestImage( 141 | for: asset, 142 | targetSize: PHImageManagerMaximumSize, 143 | contentMode: PHImageContentMode.aspectFill, 144 | options: options, 145 | resultHandler: { 146 | (image: UIImage?, info) in 147 | self.messenger.send(onChannel: "multi_image_picker/image/" + identifier, message: UIImageJPEGRepresentation(image!, 1.0)) 148 | }) 149 | 150 | if(PHInvalidImageRequestID != ID) { 151 | result(true); 152 | } 153 | } 154 | case "refreshImage": 155 | result(true) ; 156 | break ; 157 | 158 | default: 159 | result(FlutterMethodNotImplemented) 160 | } 161 | } 162 | 163 | func hexStringToUIColor (hex:String) -> UIColor { 164 | var cString:String = hex.trimmingCharacters(in: .whitespacesAndNewlines).uppercased() 165 | 166 | if (cString.hasPrefix("#")) { 167 | cString.remove(at: cString.startIndex) 168 | } 169 | 170 | if ((cString.characters.count) != 6) { 171 | return UIColor.gray 172 | } 173 | 174 | var rgbValue:UInt32 = 0 175 | Scanner(string: cString).scanHexInt32(&rgbValue) 176 | 177 | return UIColor( 178 | red: CGFloat((rgbValue & 0xFF0000) >> 16) / 255.0, 179 | green: CGFloat((rgbValue & 0x00FF00) >> 8) / 255.0, 180 | blue: CGFloat(rgbValue & 0x0000FF) / 255.0, 181 | alpha: CGFloat(1.0) 182 | ) 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /android/src/main/java/com/vitanov/multiimagepicker/MultiImagePickerPlugin.java: -------------------------------------------------------------------------------- 1 | package com.vitanov.multiimagepicker; 2 | 3 | import java.nio.file.Path; 4 | import java.nio.file.Paths; 5 | 6 | import android.app.Activity; 7 | import android.content.Context; 8 | import android.content.Intent; 9 | import android.content.pm.PackageManager; 10 | import android.database.Cursor; 11 | import android.graphics.Bitmap; 12 | import android.graphics.BitmapFactory; 13 | import android.media.MediaScannerConnection; 14 | import android.media.ThumbnailUtils; 15 | import android.net.Uri; 16 | 17 | import com.zhihu.matisse.Matisse; 18 | import com.zhihu.matisse.MimeType; 19 | import com.zhihu.matisse.engine.impl.GlideEngine; 20 | 21 | import android.content.pm.ActivityInfo; 22 | 23 | import java.io.BufferedInputStream; 24 | import java.io.ByteArrayOutputStream; 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.FileNotFoundException; 28 | import java.io.IOException; 29 | import java.io.InputStream; 30 | import java.nio.ByteBuffer; 31 | import java.nio.channels.FileChannel; 32 | import java.util.ArrayList; 33 | import java.util.Collections; 34 | import java.util.HashMap; 35 | import java.util.List; 36 | 37 | import android.Manifest; 38 | import android.os.AsyncTask; 39 | import android.os.Build; 40 | import android.os.Environment; 41 | import android.provider.MediaStore; 42 | import android.support.v4.app.ActivityCompat; 43 | import android.support.v4.content.ContextCompat; 44 | import android.util.Log; 45 | 46 | import io.flutter.plugin.common.BinaryMessenger; 47 | import io.flutter.plugin.common.MethodCall; 48 | import io.flutter.plugin.common.MethodChannel; 49 | import io.flutter.plugin.common.MethodChannel.MethodCallHandler; 50 | import io.flutter.plugin.common.MethodChannel.Result; 51 | import io.flutter.plugin.common.PluginRegistry; 52 | import io.flutter.plugin.common.PluginRegistry.Registrar; 53 | 54 | import static android.media.ThumbnailUtils.OPTIONS_RECYCLE_INPUT; 55 | 56 | 57 | /** 58 | * MultiImagePickerPlugin 59 | */ 60 | public class MultiImagePickerPlugin implements MethodCallHandler, PluginRegistry.ActivityResultListener { 61 | public interface Refresh { 62 | void after() ; 63 | } 64 | 65 | private static final String CHANNEL_NAME = "multi_image_picker"; 66 | private static final String REQUEST_THUMBNAIL = "requestThumbnail"; 67 | private static final String REQUEST_ORIGINAL = "requestOriginal"; 68 | private static final String PICK_IMAGES = "pickImages"; 69 | private static final String REFRESH_IMAGE = "refreshImage" ; 70 | private static final String MAX_IMAGES = "maxImages"; 71 | private static final int REQUEST_CODE_CHOOSE = 1001; 72 | private static final int REQUEST_CODE_GRANT_PERMISSIONS = 2001; 73 | private final MethodChannel channel; 74 | private Activity activity; 75 | private Context context; 76 | private BinaryMessenger messenger; 77 | private Result pendingResult; 78 | private MethodCall methodCall; 79 | 80 | private MultiImagePickerPlugin(Activity activity, Context context, MethodChannel channel, BinaryMessenger messenger) { 81 | this.activity = activity; 82 | this.context = context; 83 | this.channel = channel; 84 | this.messenger = messenger; 85 | } 86 | 87 | /** 88 | * Plugin registration. 89 | */ 90 | public static void registerWith(Registrar registrar) { 91 | final MethodChannel channel = new MethodChannel(registrar.messenger(), CHANNEL_NAME); 92 | MultiImagePickerPlugin instance = new MultiImagePickerPlugin(registrar.activity(), registrar.context(), channel, registrar.messenger()); 93 | registrar.addActivityResultListener(instance); 94 | channel.setMethodCallHandler(instance); 95 | } 96 | 97 | private class GetThumbnailTask extends AsyncTask { 98 | BinaryMessenger messenger; 99 | String path; 100 | String identifier; 101 | int width; 102 | int height; 103 | 104 | public GetThumbnailTask(BinaryMessenger messenger, String identifier, String path, int width, int height) { 105 | super(); 106 | this.messenger = messenger; 107 | this.identifier = identifier; 108 | this.path = path; 109 | this.width = width; 110 | this.height = height; 111 | } 112 | 113 | @Override 114 | protected Void doInBackground(String... strings) { 115 | BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 116 | bitmapOptions.inSampleSize = 10; 117 | Bitmap bitmap = ThumbnailUtils.extractThumbnail(BitmapFactory.decodeFile(this.path, bitmapOptions), this.width, this.height, OPTIONS_RECYCLE_INPUT); 118 | ByteArrayOutputStream stream = new ByteArrayOutputStream(); 119 | bitmap.compress(Bitmap.CompressFormat.JPEG, 20, stream); 120 | byte[] byteArray = stream.toByteArray(); 121 | bitmap.recycle(); 122 | 123 | 124 | final ByteBuffer buffer = ByteBuffer.allocateDirect(byteArray.length); 125 | buffer.put(byteArray); 126 | this.messenger.send("multi_image_picker/image/" + this.identifier, buffer); 127 | return null; 128 | } 129 | } 130 | 131 | private class GetImageTask extends AsyncTask { 132 | BinaryMessenger messenger; 133 | String path; 134 | String identifier; 135 | 136 | public GetImageTask(BinaryMessenger messenger, String identifier, String path) { 137 | super(); 138 | this.messenger = messenger; 139 | this.identifier = identifier; 140 | this.path = path; 141 | } 142 | 143 | @Override 144 | protected Void doInBackground(String... strings) { 145 | File file = new File(this.path); 146 | 147 | FileInputStream fileInputStream = null; 148 | byte[] bytesArray = null; 149 | 150 | try { 151 | bytesArray = new byte[(int) file.length()]; 152 | 153 | //read file into bytes[] 154 | fileInputStream = new FileInputStream(file); 155 | fileInputStream.read(bytesArray); 156 | 157 | } catch (IOException e) { 158 | e.printStackTrace(); 159 | } finally { 160 | if (fileInputStream != null) { 161 | try { 162 | fileInputStream.close(); 163 | } catch (IOException e) { 164 | e.printStackTrace(); 165 | } 166 | } 167 | 168 | } 169 | final ByteBuffer buffer = ByteBuffer.allocateDirect(bytesArray.length); 170 | buffer.put(bytesArray); 171 | this.messenger.send("multi_image_picker/image/" + this.identifier, buffer); 172 | return null; 173 | } 174 | } 175 | 176 | @Override 177 | public void onMethodCall(MethodCall call, Result result) { 178 | if (!setPendingMethodCallAndResult(call, result)) { 179 | finishWithAlreadyActiveError(); 180 | return; 181 | } 182 | 183 | if (PICK_IMAGES.equals(call.method)) { 184 | openImagePicker(); 185 | } else if (REQUEST_ORIGINAL.equals(call.method)) { 186 | String identifier = call.argument("identifier"); 187 | Uri uri = Uri.parse(identifier); 188 | String path = getDataColumn(this.context, uri, null, null); 189 | 190 | GetImageTask task = new GetImageTask(this.messenger, identifier, path); 191 | task.execute(""); 192 | finishWithSuccess(true); 193 | 194 | } else if (REQUEST_THUMBNAIL.equals(call.method)) { 195 | String identifier = call.argument("identifier"); 196 | Integer width = call.argument("width"); 197 | Integer height = call.argument("height"); 198 | Uri uri = Uri.parse(identifier); 199 | String path = getDataColumn(this.context, uri, null, null); 200 | 201 | GetThumbnailTask task = new GetThumbnailTask(this.messenger, identifier, path, width, height); 202 | task.execute(""); 203 | finishWithSuccess(true); 204 | } else if (REFRESH_IMAGE.equals(call.method)) { 205 | String path = call.argument("path") ; 206 | refreshGallery(path); 207 | } else { 208 | pendingResult.notImplemented(); 209 | } 210 | } 211 | 212 | private void openImagePicker() { 213 | 214 | if (ContextCompat.checkSelfPermission(this.activity, 215 | Manifest.permission.READ_EXTERNAL_STORAGE) 216 | != PackageManager.PERMISSION_GRANTED) { 217 | // Permission is not granted 218 | // Should we show an explanation? 219 | if (ActivityCompat.shouldShowRequestPermissionRationale(this.activity, 220 | Manifest.permission.READ_EXTERNAL_STORAGE)) { 221 | // Show an explanation to the user *asynchronously* -- don't block 222 | // this thread waiting for the user's response! After the user 223 | // sees the explanation, try again to request the permission. 224 | } else { 225 | // No explanation needed; request the permission 226 | ActivityCompat.requestPermissions(this.activity, 227 | new String[]{Manifest.permission.READ_EXTERNAL_STORAGE}, 228 | REQUEST_CODE_GRANT_PERMISSIONS); 229 | } 230 | clearMethodCallAndResult(); 231 | } else { 232 | presentPicker(); 233 | } 234 | 235 | } 236 | 237 | private void refreshGallery(String path) { 238 | try { 239 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 240 | MediaScannerConnection.scanFile(context, new String[]{path}, null, new MediaScannerConnection.OnScanCompletedListener() { 241 | public void onScanCompleted(String path, Uri uri) { 242 | Log.i("ExternalStorage", "Scanned " + path + ":"); 243 | Log.i("ExternalStorage", "-> uri=" + uri); 244 | finishWithSuccess(true); 245 | } 246 | }); 247 | } else { 248 | context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://" + Environment.getExternalStorageDirectory()))); 249 | finishWithSuccess(true); 250 | } 251 | } catch (Exception e) { 252 | finishWithError("unknown error", e.toString()); 253 | } 254 | } 255 | 256 | private void presentPicker() { 257 | int maxImages = MultiImagePickerPlugin.this.methodCall.argument(MAX_IMAGES); 258 | Matisse.from(MultiImagePickerPlugin.this.activity) 259 | .choose(MimeType.ofImage()) 260 | .countable(true) 261 | .maxSelectable(maxImages) 262 | .restrictOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) 263 | .imageEngine(new GlideEngine()) 264 | .forResult(REQUEST_CODE_CHOOSE); 265 | } 266 | 267 | private static String getDataColumn(Context context, Uri uri, String selection, 268 | String[] selectionArgs) { 269 | 270 | Cursor cursor = null; 271 | final String column = "_data"; 272 | final String[] projection = { 273 | column 274 | }; 275 | 276 | try { 277 | cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null); 278 | Log.d("DEBUG_MSG" , "url : " + uri.toString()) ; 279 | 280 | if (cursor != null && cursor.moveToFirst()) { 281 | final int column_index = cursor.getColumnIndexOrThrow(column); 282 | return cursor.getString(column_index); 283 | } 284 | } finally { 285 | if (cursor != null) 286 | cursor.close(); 287 | } 288 | return null; 289 | } 290 | 291 | 292 | @Override 293 | public boolean onActivityResult(int requestCode, int resultCode, Intent data) { 294 | if (requestCode == REQUEST_CODE_CHOOSE && resultCode == Activity.RESULT_OK) { 295 | List photos = Matisse.obtainResult(data); 296 | List> result = new ArrayList<>(photos.size()); 297 | for (Uri uri : photos) { 298 | HashMap map = new HashMap<>(); 299 | map.put("identifier", uri.toString()); 300 | map.put("filePath", FileDirectory.getPath(context, uri)) ; 301 | InputStream is = null; 302 | Integer width = 0; 303 | Integer height = 0; 304 | try { 305 | is = context.getContentResolver().openInputStream(uri); 306 | BitmapFactory.Options dbo = new BitmapFactory.Options(); 307 | dbo.inJustDecodeBounds = true; 308 | BitmapFactory.decodeStream(is, null, dbo); 309 | is.close(); 310 | width = dbo.outWidth; 311 | height = dbo.outHeight; 312 | } catch (FileNotFoundException e) { 313 | e.printStackTrace(); 314 | } catch (IOException e) { 315 | e.printStackTrace(); 316 | } 317 | 318 | map.put("width", width); 319 | map.put("height", height); 320 | result.add(map); 321 | } 322 | finishWithSuccess(result); 323 | return true; 324 | } else if (requestCode == REQUEST_CODE_GRANT_PERMISSIONS && resultCode == Activity.RESULT_OK) { 325 | presentPicker(); 326 | return true; 327 | } else { 328 | finishWithSuccess(Collections.emptyList()); 329 | clearMethodCallAndResult(); 330 | } 331 | return false; 332 | } 333 | 334 | private void finishWithSuccess(List imagePathList) { 335 | if (pendingResult != null) 336 | pendingResult.success(imagePathList); 337 | clearMethodCallAndResult(); 338 | } 339 | 340 | private void finishWithSuccess(Boolean result) { 341 | if (pendingResult != null) 342 | pendingResult.success(result); 343 | clearMethodCallAndResult(); 344 | } 345 | 346 | private void finishWithAlreadyActiveError() { 347 | finishWithError("already_active", "Image picker is already active"); 348 | } 349 | 350 | private void finishWithError(String errorCode, String errorMessage) { 351 | if (pendingResult != null) 352 | pendingResult.error(errorCode, errorMessage, null); 353 | clearMethodCallAndResult(); 354 | } 355 | 356 | private void clearMethodCallAndResult() { 357 | methodCall = null; 358 | pendingResult = null; 359 | } 360 | 361 | private boolean setPendingMethodCallAndResult( 362 | MethodCall methodCall, MethodChannel.Result result) { 363 | if (pendingResult != null) { 364 | return false; 365 | } 366 | 367 | this.methodCall = methodCall; 368 | pendingResult = result; 369 | return true; 370 | } 371 | } 372 | -------------------------------------------------------------------------------- /example/ios/Runner.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 46; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; 11 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */ = {isa = PBXBuildFile; fileRef = 2D5378251FAA1A9400D5DBA9 /* flutter_assets */; }; 12 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; 13 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; }; 14 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3B80C3931E831B6300D905FE /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 15 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; }; 16 | 8D00C53A24EE7AD65BF7F869 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4C872365D093B70B9D8799C3 /* Pods_Runner.framework */; }; 17 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; }; 18 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9740EEBA1CF902C7004384FC /* Flutter.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 19 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; 20 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; 21 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 22 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 23 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 9705A1C41CF9048500538489 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | 3B80C3951E831B6300D905FE /* App.framework in Embed Frameworks */, 34 | 9705A1C71CF904A300538489 /* Flutter.framework in Embed Frameworks */, 35 | ); 36 | name = "Embed Frameworks"; 37 | runOnlyForDeploymentPostprocessing = 0; 38 | }; 39 | /* End PBXCopyFilesBuildPhase section */ 40 | 41 | /* Begin PBXFileReference section */ 42 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 43 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 44 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */ = {isa = PBXFileReference; lastKnownFileType = folder; name = flutter_assets; path = Flutter/flutter_assets; sourceTree = SOURCE_ROOT; }; 45 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; 46 | 3B80C3931E831B6300D905FE /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/App.framework; sourceTree = ""; }; 47 | 4C872365D093B70B9D8799C3 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = ""; }; 49 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 50 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 51 | 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 52 | 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; 53 | 9740EEBA1CF902C7004384FC /* Flutter.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Flutter.framework; path = Flutter/Flutter.framework; sourceTree = ""; }; 54 | 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; 55 | 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 56 | 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 57 | 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 58 | 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 59 | /* End PBXFileReference section */ 60 | 61 | /* Begin PBXFrameworksBuildPhase section */ 62 | 97C146EB1CF9000F007C117D /* Frameworks */ = { 63 | isa = PBXFrameworksBuildPhase; 64 | buildActionMask = 2147483647; 65 | files = ( 66 | 9705A1C61CF904A100538489 /* Flutter.framework in Frameworks */, 67 | 3B80C3941E831B6300D905FE /* App.framework in Frameworks */, 68 | 8D00C53A24EE7AD65BF7F869 /* Pods_Runner.framework in Frameworks */, 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | /* End PBXFrameworksBuildPhase section */ 73 | 74 | /* Begin PBXGroup section */ 75 | 5FF3A01436DFCD8D17786460 /* Frameworks */ = { 76 | isa = PBXGroup; 77 | children = ( 78 | 4C872365D093B70B9D8799C3 /* Pods_Runner.framework */, 79 | ); 80 | name = Frameworks; 81 | sourceTree = ""; 82 | }; 83 | 8F1C8D7FCE8C5C17BAC1272F /* Pods */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | ); 87 | name = Pods; 88 | sourceTree = ""; 89 | }; 90 | 9740EEB11CF90186004384FC /* Flutter */ = { 91 | isa = PBXGroup; 92 | children = ( 93 | 2D5378251FAA1A9400D5DBA9 /* flutter_assets */, 94 | 3B80C3931E831B6300D905FE /* App.framework */, 95 | 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */, 96 | 9740EEBA1CF902C7004384FC /* Flutter.framework */, 97 | 9740EEB21CF90195004384FC /* Debug.xcconfig */, 98 | 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, 99 | 9740EEB31CF90195004384FC /* Generated.xcconfig */, 100 | ); 101 | name = Flutter; 102 | sourceTree = ""; 103 | }; 104 | 97C146E51CF9000F007C117D = { 105 | isa = PBXGroup; 106 | children = ( 107 | 9740EEB11CF90186004384FC /* Flutter */, 108 | 97C146F01CF9000F007C117D /* Runner */, 109 | 97C146EF1CF9000F007C117D /* Products */, 110 | 8F1C8D7FCE8C5C17BAC1272F /* Pods */, 111 | 5FF3A01436DFCD8D17786460 /* Frameworks */, 112 | ); 113 | sourceTree = ""; 114 | }; 115 | 97C146EF1CF9000F007C117D /* Products */ = { 116 | isa = PBXGroup; 117 | children = ( 118 | 97C146EE1CF9000F007C117D /* Runner.app */, 119 | ); 120 | name = Products; 121 | sourceTree = ""; 122 | }; 123 | 97C146F01CF9000F007C117D /* Runner */ = { 124 | isa = PBXGroup; 125 | children = ( 126 | 97C146FA1CF9000F007C117D /* Main.storyboard */, 127 | 97C146FD1CF9000F007C117D /* Assets.xcassets */, 128 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 129 | 97C147021CF9000F007C117D /* Info.plist */, 130 | 97C146F11CF9000F007C117D /* Supporting Files */, 131 | 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, 132 | 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, 133 | 74858FAE1ED2DC5600515810 /* AppDelegate.swift */, 134 | 74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */, 135 | ); 136 | path = Runner; 137 | sourceTree = ""; 138 | }; 139 | 97C146F11CF9000F007C117D /* Supporting Files */ = { 140 | isa = PBXGroup; 141 | children = ( 142 | ); 143 | name = "Supporting Files"; 144 | sourceTree = ""; 145 | }; 146 | /* End PBXGroup section */ 147 | 148 | /* Begin PBXNativeTarget section */ 149 | 97C146ED1CF9000F007C117D /* Runner */ = { 150 | isa = PBXNativeTarget; 151 | buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; 152 | buildPhases = ( 153 | 0DF0771D2DC934794088A7FF /* [CP] Check Pods Manifest.lock */, 154 | 9740EEB61CF901F6004384FC /* Run Script */, 155 | 97C146EA1CF9000F007C117D /* Sources */, 156 | 97C146EB1CF9000F007C117D /* Frameworks */, 157 | 97C146EC1CF9000F007C117D /* Resources */, 158 | 9705A1C41CF9048500538489 /* Embed Frameworks */, 159 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */, 160 | 52D1BDE720297B920EA6DEA7 /* [CP] Embed Pods Frameworks */, 161 | ); 162 | buildRules = ( 163 | ); 164 | dependencies = ( 165 | ); 166 | name = Runner; 167 | productName = Runner; 168 | productReference = 97C146EE1CF9000F007C117D /* Runner.app */; 169 | productType = "com.apple.product-type.application"; 170 | }; 171 | /* End PBXNativeTarget section */ 172 | 173 | /* Begin PBXProject section */ 174 | 97C146E61CF9000F007C117D /* Project object */ = { 175 | isa = PBXProject; 176 | attributes = { 177 | LastUpgradeCheck = 0910; 178 | ORGANIZATIONNAME = "The Chromium Authors"; 179 | TargetAttributes = { 180 | 97C146ED1CF9000F007C117D = { 181 | CreatedOnToolsVersion = 7.3.1; 182 | DevelopmentTeam = 4BJWF2898T; 183 | LastSwiftMigration = 0910; 184 | }; 185 | }; 186 | }; 187 | buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; 188 | compatibilityVersion = "Xcode 3.2"; 189 | developmentRegion = English; 190 | hasScannedForEncodings = 0; 191 | knownRegions = ( 192 | en, 193 | Base, 194 | ); 195 | mainGroup = 97C146E51CF9000F007C117D; 196 | productRefGroup = 97C146EF1CF9000F007C117D /* Products */; 197 | projectDirPath = ""; 198 | projectRoot = ""; 199 | targets = ( 200 | 97C146ED1CF9000F007C117D /* Runner */, 201 | ); 202 | }; 203 | /* End PBXProject section */ 204 | 205 | /* Begin PBXResourcesBuildPhase section */ 206 | 97C146EC1CF9000F007C117D /* Resources */ = { 207 | isa = PBXResourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, 211 | 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 212 | 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, 213 | 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, 214 | 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 215 | 2D5378261FAA1A9400D5DBA9 /* flutter_assets in Resources */, 216 | 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXResourcesBuildPhase section */ 221 | 222 | /* Begin PBXShellScriptBuildPhase section */ 223 | 0DF0771D2DC934794088A7FF /* [CP] Check Pods Manifest.lock */ = { 224 | isa = PBXShellScriptBuildPhase; 225 | buildActionMask = 2147483647; 226 | files = ( 227 | ); 228 | inputPaths = ( 229 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 230 | "${PODS_ROOT}/Manifest.lock", 231 | ); 232 | name = "[CP] Check Pods Manifest.lock"; 233 | outputPaths = ( 234 | "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", 235 | ); 236 | runOnlyForDeploymentPostprocessing = 0; 237 | shellPath = /bin/sh; 238 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 239 | showEnvVarsInLog = 0; 240 | }; 241 | 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { 242 | isa = PBXShellScriptBuildPhase; 243 | buildActionMask = 2147483647; 244 | files = ( 245 | ); 246 | inputPaths = ( 247 | ); 248 | name = "Thin Binary"; 249 | outputPaths = ( 250 | ); 251 | runOnlyForDeploymentPostprocessing = 0; 252 | shellPath = /bin/sh; 253 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" thin"; 254 | }; 255 | 52D1BDE720297B920EA6DEA7 /* [CP] Embed Pods Frameworks */ = { 256 | isa = PBXShellScriptBuildPhase; 257 | buildActionMask = 2147483647; 258 | files = ( 259 | ); 260 | inputPaths = ( 261 | "${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", 262 | "${BUILT_PRODUCTS_DIR}/BSGridCollectionViewLayout/BSGridCollectionViewLayout.framework", 263 | "${BUILT_PRODUCTS_DIR}/BSImagePicker/BSImagePicker.framework", 264 | "${BUILT_PRODUCTS_DIR}/BSImageView/BSImageView.framework", 265 | "${PODS_ROOT}/../.symlinks/flutter/ios/Flutter.framework", 266 | "${BUILT_PRODUCTS_DIR}/multi_image_picker/multi_image_picker.framework", 267 | ); 268 | name = "[CP] Embed Pods Frameworks"; 269 | outputPaths = ( 270 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSGridCollectionViewLayout.framework", 271 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImagePicker.framework", 272 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BSImageView.framework", 273 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", 274 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/multi_image_picker.framework", 275 | ); 276 | runOnlyForDeploymentPostprocessing = 0; 277 | shellPath = /bin/sh; 278 | shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; 279 | showEnvVarsInLog = 0; 280 | }; 281 | 9740EEB61CF901F6004384FC /* Run Script */ = { 282 | isa = PBXShellScriptBuildPhase; 283 | buildActionMask = 2147483647; 284 | files = ( 285 | ); 286 | inputPaths = ( 287 | ); 288 | name = "Run Script"; 289 | outputPaths = ( 290 | ); 291 | runOnlyForDeploymentPostprocessing = 0; 292 | shellPath = /bin/sh; 293 | shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; 294 | }; 295 | /* End PBXShellScriptBuildPhase section */ 296 | 297 | /* Begin PBXSourcesBuildPhase section */ 298 | 97C146EA1CF9000F007C117D /* Sources */ = { 299 | isa = PBXSourcesBuildPhase; 300 | buildActionMask = 2147483647; 301 | files = ( 302 | 74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */, 303 | 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, 304 | ); 305 | runOnlyForDeploymentPostprocessing = 0; 306 | }; 307 | /* End PBXSourcesBuildPhase section */ 308 | 309 | /* Begin PBXVariantGroup section */ 310 | 97C146FA1CF9000F007C117D /* Main.storyboard */ = { 311 | isa = PBXVariantGroup; 312 | children = ( 313 | 97C146FB1CF9000F007C117D /* Base */, 314 | ); 315 | name = Main.storyboard; 316 | sourceTree = ""; 317 | }; 318 | 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = { 319 | isa = PBXVariantGroup; 320 | children = ( 321 | 97C147001CF9000F007C117D /* Base */, 322 | ); 323 | name = LaunchScreen.storyboard; 324 | sourceTree = ""; 325 | }; 326 | /* End PBXVariantGroup section */ 327 | 328 | /* Begin XCBuildConfiguration section */ 329 | 97C147031CF9000F007C117D /* Debug */ = { 330 | isa = XCBuildConfiguration; 331 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 332 | buildSettings = { 333 | ALWAYS_SEARCH_USER_PATHS = NO; 334 | CLANG_ANALYZER_NONNULL = YES; 335 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 336 | CLANG_CXX_LIBRARY = "libc++"; 337 | CLANG_ENABLE_MODULES = YES; 338 | CLANG_ENABLE_OBJC_ARC = YES; 339 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 340 | CLANG_WARN_BOOL_CONVERSION = YES; 341 | CLANG_WARN_COMMA = YES; 342 | CLANG_WARN_CONSTANT_CONVERSION = YES; 343 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 344 | CLANG_WARN_EMPTY_BODY = YES; 345 | CLANG_WARN_ENUM_CONVERSION = YES; 346 | CLANG_WARN_INFINITE_RECURSION = YES; 347 | CLANG_WARN_INT_CONVERSION = YES; 348 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 349 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 350 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 351 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 352 | CLANG_WARN_STRICT_PROTOTYPES = YES; 353 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 354 | CLANG_WARN_UNREACHABLE_CODE = YES; 355 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 356 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 357 | COPY_PHASE_STRIP = NO; 358 | DEBUG_INFORMATION_FORMAT = dwarf; 359 | ENABLE_STRICT_OBJC_MSGSEND = YES; 360 | ENABLE_TESTABILITY = YES; 361 | GCC_C_LANGUAGE_STANDARD = gnu99; 362 | GCC_DYNAMIC_NO_PIC = NO; 363 | GCC_NO_COMMON_BLOCKS = YES; 364 | GCC_OPTIMIZATION_LEVEL = 0; 365 | GCC_PREPROCESSOR_DEFINITIONS = ( 366 | "DEBUG=1", 367 | "$(inherited)", 368 | ); 369 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 370 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 371 | GCC_WARN_UNDECLARED_SELECTOR = YES; 372 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 373 | GCC_WARN_UNUSED_FUNCTION = YES; 374 | GCC_WARN_UNUSED_VARIABLE = YES; 375 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 376 | MTL_ENABLE_DEBUG_INFO = YES; 377 | ONLY_ACTIVE_ARCH = YES; 378 | SDKROOT = iphoneos; 379 | TARGETED_DEVICE_FAMILY = "1,2"; 380 | }; 381 | name = Debug; 382 | }; 383 | 97C147041CF9000F007C117D /* Release */ = { 384 | isa = XCBuildConfiguration; 385 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_ANALYZER_NONNULL = YES; 389 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; 390 | CLANG_CXX_LIBRARY = "libc++"; 391 | CLANG_ENABLE_MODULES = YES; 392 | CLANG_ENABLE_OBJC_ARC = YES; 393 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 394 | CLANG_WARN_BOOL_CONVERSION = YES; 395 | CLANG_WARN_COMMA = YES; 396 | CLANG_WARN_CONSTANT_CONVERSION = YES; 397 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 398 | CLANG_WARN_EMPTY_BODY = YES; 399 | CLANG_WARN_ENUM_CONVERSION = YES; 400 | CLANG_WARN_INFINITE_RECURSION = YES; 401 | CLANG_WARN_INT_CONVERSION = YES; 402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 403 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 404 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 405 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 406 | CLANG_WARN_STRICT_PROTOTYPES = YES; 407 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 408 | CLANG_WARN_UNREACHABLE_CODE = YES; 409 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 410 | "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; 411 | COPY_PHASE_STRIP = NO; 412 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 413 | ENABLE_NS_ASSERTIONS = NO; 414 | ENABLE_STRICT_OBJC_MSGSEND = YES; 415 | GCC_C_LANGUAGE_STANDARD = gnu99; 416 | GCC_NO_COMMON_BLOCKS = YES; 417 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 418 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 419 | GCC_WARN_UNDECLARED_SELECTOR = YES; 420 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 421 | GCC_WARN_UNUSED_FUNCTION = YES; 422 | GCC_WARN_UNUSED_VARIABLE = YES; 423 | IPHONEOS_DEPLOYMENT_TARGET = 8.0; 424 | MTL_ENABLE_DEBUG_INFO = NO; 425 | SDKROOT = iphoneos; 426 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 427 | TARGETED_DEVICE_FAMILY = "1,2"; 428 | VALIDATE_PRODUCT = YES; 429 | }; 430 | name = Release; 431 | }; 432 | 97C147061CF9000F007C117D /* Debug */ = { 433 | isa = XCBuildConfiguration; 434 | baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; 435 | buildSettings = { 436 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 437 | CLANG_ENABLE_MODULES = YES; 438 | CURRENT_PROJECT_VERSION = 1; 439 | DEVELOPMENT_TEAM = 4BJWF2898T; 440 | ENABLE_BITCODE = NO; 441 | FRAMEWORK_SEARCH_PATHS = ( 442 | "$(inherited)", 443 | "$(PROJECT_DIR)/Flutter", 444 | ); 445 | INFOPLIST_FILE = Runner/Info.plist; 446 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 447 | LIBRARY_SEARCH_PATHS = ( 448 | "$(inherited)", 449 | "$(PROJECT_DIR)/Flutter", 450 | ); 451 | PRODUCT_BUNDLE_IDENTIFIER = com.vitanov.multiImagePickerExample; 452 | PRODUCT_NAME = "$(TARGET_NAME)"; 453 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 454 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 455 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 456 | SWIFT_VERSION = 4.0; 457 | VERSIONING_SYSTEM = "apple-generic"; 458 | }; 459 | name = Debug; 460 | }; 461 | 97C147071CF9000F007C117D /* Release */ = { 462 | isa = XCBuildConfiguration; 463 | baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; 464 | buildSettings = { 465 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 466 | CLANG_ENABLE_MODULES = YES; 467 | CURRENT_PROJECT_VERSION = 1; 468 | DEVELOPMENT_TEAM = 4BJWF2898T; 469 | ENABLE_BITCODE = NO; 470 | FRAMEWORK_SEARCH_PATHS = ( 471 | "$(inherited)", 472 | "$(PROJECT_DIR)/Flutter", 473 | ); 474 | INFOPLIST_FILE = Runner/Info.plist; 475 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; 476 | LIBRARY_SEARCH_PATHS = ( 477 | "$(inherited)", 478 | "$(PROJECT_DIR)/Flutter", 479 | ); 480 | PRODUCT_BUNDLE_IDENTIFIER = com.vitanov.multiImagePickerExample; 481 | PRODUCT_NAME = "$(TARGET_NAME)"; 482 | SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h"; 483 | SWIFT_SWIFT3_OBJC_INFERENCE = On; 484 | SWIFT_VERSION = 4.0; 485 | VERSIONING_SYSTEM = "apple-generic"; 486 | }; 487 | name = Release; 488 | }; 489 | /* End XCBuildConfiguration section */ 490 | 491 | /* Begin XCConfigurationList section */ 492 | 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = { 493 | isa = XCConfigurationList; 494 | buildConfigurations = ( 495 | 97C147031CF9000F007C117D /* Debug */, 496 | 97C147041CF9000F007C117D /* Release */, 497 | ); 498 | defaultConfigurationIsVisible = 0; 499 | defaultConfigurationName = Release; 500 | }; 501 | 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = { 502 | isa = XCConfigurationList; 503 | buildConfigurations = ( 504 | 97C147061CF9000F007C117D /* Debug */, 505 | 97C147071CF9000F007C117D /* Release */, 506 | ); 507 | defaultConfigurationIsVisible = 0; 508 | defaultConfigurationName = Release; 509 | }; 510 | /* End XCConfigurationList section */ 511 | }; 512 | rootObject = 97C146E61CF9000F007C117D /* Project object */; 513 | } 514 | --------------------------------------------------------------------------------