diff --git a/README.md b/README.md index f8e891c..354ad77 100644 --- a/README.md +++ b/README.md @@ -9,64 +9,100 @@ Android library that can be used as quick solution to ImagePicker feature implem - Camera photo picker - Gallery single photo picker - Gallery multiple photo picker +- Custom gallery picker, supports multiple selection (for old non-AOSP Android ROMs that does not support multiple selection intent) ## Implementation 1. In project-level gradle add new maven repository: -
+```groovy
 allprojects {
     repositories {
-        ...
         maven { url 'https://jitpack.io' }
     }
 }
-
+``` 2. In app-level gradle add new implementation: -
+```groovy
 dependencies {
-    implementation 'com.github.ShiftHackZ:ImagePicker:v1.0'
+    implementation 'com.github.ShiftHackZ:ImagePicker:v2.0'
 }
-
- -3. In order to receive images, implement ImagePickerCallback in your Fragment/Activity or as object: - -
-public class MainActivity extends AppCompatActivity implements ImagePickerCallback {
-    ...
-    @Override
-    public void onImagesSelected(List files) {
-        // Do whatever you want with list of files
-        for (int i = 0; i < files.size(); i++) {
-            // As example you can process each file inside for-cycle
-        }        
-    }    
-    ...
+```
+
+3. Create file `provider_path.xml` in `res/xml` folder:
+
+```xml
+
+
+    
+    
+
+```
+
+4. In your `AndroidManifest.xml` add the file provider inside the `
+    
+
+```
+
+5. In order to receive images, implement `ImagePickerCallback` in your Fragment/Activity or as object:
+
+```kotlin
+class MainActivity : AppCompatActivity(), ImagePickerCallback {
+
+    override fun onImagePickerResult(result: PickedResult) {
+        when (result) {
+            PickedResult.Empty -> {
+                // No file was selected, noting to do
+            }
+            is PickedResult.Error -> {
+                val throwable = result.throwable
+                // Some error happened, handle this throwable
+            }
+            is PickedResult.Multiple -> {
+                val pickedImages = result.images
+                val files = pickedImages.map { it.file }
+                // Selected multiple images, do whatever you want with files
+            }
+            is PickedResult.Single -> {
+                val pickedImage = result.image
+                val file = pickedImage.file
+                // Selected one image, do whatever you want with file
+            }
+        }
+    }
 }
-
+``` -4. Create an instance of ImagePicker using ImagePicker.Builder(), which require 2 mandatory params: current Activity and ImagePickerCallback: +6. Create an instance of ImagePicker using ImagePicker.Builder(), which require 2 mandatory params: current Activity and ImagePickerCallback: -
-ImagePicker imagePicker = new ImagePicker.Builder(activity, callback)
-    .useGallery(true)
-    .useCamera(true)
-    .useMultiSelection(true)
-    .build();
-
+```kotlin +val imagePicker = ImagePicker.Builder(this.packageName + ".provider", this) + .useGallery(true) // Use gallery picker if true + .useCamera(true) // Use camera picker if true + .multipleSelection() // Allow multiple selection in gallery picker + .minimumSelectionCount(2) // Defines min count of GallerySelector.CUSTOM multiple selection gallery picker + .maximumSelectionCount(3) // Defines max count of GallerySelector.CUSTOM multiple selection gallery picker + .gallerySelector(GallerySelector.CUSTOM) // Available values: GallerySelector.NATIVE, GallerySelector.CUSTOM + .build() +``` -List of Builder methods: -- useGallery(boolean) // Pass 'true' if you want to enable gallery picker -- useMultiSelection(boolean) // Pass 'true' if you need gallery picker to support multiple photo selection -- useCamera(boolean) // Pass 'true' if you want to enable camera picker -5. Finally, launch your ImagePicker: +7. Finally, launch your ImagePicker: -
-imagePicker.start();
-
+```kotlin +imagePicker.launch(context) +``` ## Credits - Developer: Dmitriy Moroz diff --git a/app/build.gradle b/app/build.gradle index 1301d75..8f6de73 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,15 +1,16 @@ plugins { id 'com.android.application' + id 'kotlin-android' + id 'kotlin-kapt' } android { - compileSdkVersion 31 - buildToolsVersion "30.0.3" + compileSdkVersion 33 defaultConfig { applicationId "com.shz.imagepicker.imagepickerapp" minSdkVersion 16 - targetSdkVersion 31 + targetSdkVersion 33 versionCode 1 versionName "1.0" @@ -21,6 +22,9 @@ android { minifyEnabled false } } + buildFeatures { + dataBinding true + } compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 @@ -29,9 +33,9 @@ android { dependencies { implementation project(':imagepicker') - implementation 'com.github.bumptech.glide:glide:4.12.0' - annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0' - implementation 'androidx.appcompat:appcompat:1.3.1' - implementation 'com.google.android.material:material:1.4.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.1' + implementation 'com.github.bumptech.glide:glide:4.13.2' + kapt 'com.github.bumptech.glide:compiler:4.13.2' + implementation 'androidx.appcompat:appcompat:1.5.1' + implementation 'com.google.android.material:material:1.6.1' + implementation 'androidx.constraintlayout:constraintlayout:2.1.4' } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bd47d86..0d57f76 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -3,6 +3,14 @@ package="com.shz.imagepicker.imagepickerapp"> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/shz/imagepicker/imagepickerapp/ImagesDemoAdapter.kt b/app/src/main/java/com/shz/imagepicker/imagepickerapp/ImagesDemoAdapter.kt new file mode 100644 index 0000000..502ae05 --- /dev/null +++ b/app/src/main/java/com/shz/imagepicker/imagepickerapp/ImagesDemoAdapter.kt @@ -0,0 +1,47 @@ +package com.shz.imagepicker.imagepickerapp + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.ListAdapter +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.shz.imagepicker.imagepicker.model.PickedImage +import com.shz.imagepicker.imagepickerapp.databinding.ItemDemoImageBinding + +class ImagesDemoAdapter : ListAdapter(diff) { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ViewHolder( + ItemDemoImageBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + + override fun onBindViewHolder(holder: ViewHolder, position: Int) { + holder.bind(getItem(position)) + } + + inner class ViewHolder( + private val binding: ItemDemoImageBinding, + ) : RecyclerView.ViewHolder(binding.root) { + + fun bind(item: PickedImage) { + Glide.with(binding.image) + .load(item.file) + .centerCrop() + .into(binding.image) + } + } + + companion object { + private val diff = object : DiffUtil.ItemCallback() { + override fun areItemsTheSame( + oldItem: PickedImage, + newItem: PickedImage + ): Boolean = oldItem == newItem + + override fun areContentsTheSame( + oldItem: PickedImage, + newItem: PickedImage + ): Boolean = oldItem == newItem + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.java b/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.java deleted file mode 100644 index 0c560ce..0000000 --- a/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.java +++ /dev/null @@ -1,105 +0,0 @@ -package com.shz.imagepicker.imagepickerapp; - -import androidx.appcompat.app.AppCompatActivity; - -import android.os.Bundle; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.bumptech.glide.Glide; -import com.shz.imagepicker.imagepicker.ImagePicker; -import com.shz.imagepicker.imagepicker.ImagePickerCallback; - -import java.io.File; -import java.util.List; - -public class MainActivity extends AppCompatActivity implements ImagePickerCallback { - - private ImageView mResultImage; - private LinearLayout mResultContainer; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - mResultImage = (ImageView) findViewById(R.id.iv_result); - mResultContainer = (LinearLayout) findViewById(R.id.ll_container); - } - - @Override - protected void onResume() { - super.onResume(); - } - - @Override - public void onImagesSelected(List files) { - if (files.size() > 0) { - if (files.size() == 1) { - Glide.with(this) - .load(files.get(0)) - .into(mResultImage); - - mResultContainer.setVisibility(View.GONE); - mResultImage.setVisibility(View.VISIBLE); - } else { - mResultContainer.removeAllViews(); - for (int i = 0; i < files.size(); i++) { - ImageView imageView = new ImageView(this); - - Glide.with(this) - .load(files.get(i)) - .into(imageView); - - mResultContainer.addView(imageView); - imageView.getLayoutParams().height = 300; - imageView.requestLayout(); - } - mResultContainer.setVisibility(View.VISIBLE); - mResultImage.setVisibility(View.GONE); - } - } - } - - public void onCameraClick(View v) { - new ImagePicker.Builder(this, this) - .useCamera(true) - .build() - .start(); - } - - public void onGalleryMultipleClick(View v) { - new ImagePicker.Builder(this, this) - .useGallery(true) - .useMultiSelection(true) - .build() - .start(); - } - - public void onGalleryClick(View v) { - new ImagePicker.Builder(this, this) - .useGallery(true) - .build() - .start(); - } - - public void onGenericMultipleClick(View v) { - new ImagePicker.Builder(this, this) - .useGallery(true) - .useCamera(true) - .useMultiSelection(true) - .build() - .start(); - } - - public void onGenericClick(View v) { - new ImagePicker.Builder(this, this) - .useGallery(true) - .useCamera(true) - .build() - .start(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.kt b/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.kt new file mode 100644 index 0000000..38ad354 --- /dev/null +++ b/app/src/main/java/com/shz/imagepicker/imagepickerapp/MainActivity.kt @@ -0,0 +1,106 @@ +package com.shz.imagepicker.imagepickerapp + +import android.os.Bundle +import android.util.Log +import android.widget.ImageView +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.GridLayoutManager +import com.bumptech.glide.Glide +import com.shz.imagepicker.imagepicker.ImagePicker +import com.shz.imagepicker.imagepicker.ImagePickerCallback +import com.shz.imagepicker.imagepicker.ImagePickerLoadDelegate +import com.shz.imagepicker.imagepicker.model.GalleryPicker +import com.shz.imagepicker.imagepicker.model.PickedResult +import com.shz.imagepicker.imagepickerapp.databinding.ActivityMainBinding +import java.io.File + +class MainActivity : AppCompatActivity(), ImagePickerCallback { + + private val imagePicker: ImagePicker.Builder + get() = ImagePicker.Builder(this.packageName + ".provider", this) + + private lateinit var binding: ActivityMainBinding + private lateinit var adapter: ImagesDemoAdapter + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + + adapter = ImagesDemoAdapter() + + binding.rv.adapter = adapter + binding.rv.layoutManager = GridLayoutManager(this, 2) + + binding.btnCamera.setOnClickListener { + imagePicker + .autoRotate(binding.switchAutoRotate.isChecked) + .useCamera() + .build() + .launch(this) + } + binding.btnGallery.setOnClickListener { + imagePicker + .autoRotate(binding.switchAutoRotate.isChecked) + .useGallery() +// .multipleSelection(true) + .build() + .launch(this) + } + binding.btnGalCustomSingle.setOnClickListener { + imagePicker + .autoRotate(binding.switchAutoRotate.isChecked) + .useGallery() + .galleryPicker(GalleryPicker.CUSTOM) + .loadDelegate { imageView, file -> + Glide.with(this) + .load(file) + .into(imageView) + } + .build() + .launch(this) + } + binding.btnGalCustomMultiple.setOnClickListener { + imagePicker + .autoRotate(binding.switchAutoRotate.isChecked) + .useGallery() + .multipleSelection() + .galleryPicker(GalleryPicker.CUSTOM) + .loadDelegate { imageView, file -> + Glide.with(this) + .load(file) + .into(imageView) + } + .build() + .launch(this) + } + binding.btnDialog.setOnClickListener { + imagePicker + .autoRotate(binding.switchAutoRotate.isChecked) + .useGallery() + .useCamera() + .multipleSelection() + .galleryPicker(GalleryPicker.NATIVE) + .build() + .launch(this) + } + } + + override fun onImagePickerResult(result: PickedResult) { + Log.d("MainActivity", "result: $result") + when (result) { + PickedResult.Empty -> { + adapter.submitList(emptyList()) + } + is PickedResult.Error -> { + result.throwable.printStackTrace() + } + is PickedResult.Multiple -> { + adapter.submitList(result.images) + } + is PickedResult.Single -> { + adapter.submitList(listOf(result.image)) + } + } + } +} diff --git a/app/src/main/res/drawable/ic_camera.xml b/app/src/main/res/drawable/ic_camera.xml new file mode 100644 index 0000000..a5d172e --- /dev/null +++ b/app/src/main/res/drawable/ic_camera.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_custom.xml b/app/src/main/res/drawable/ic_custom.xml new file mode 100644 index 0000000..f7ca6f7 --- /dev/null +++ b/app/src/main/res/drawable/ic_custom.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_gallery.xml b/app/src/main/res/drawable/ic_gallery.xml new file mode 100644 index 0000000..3df2119 --- /dev/null +++ b/app/src/main/res/drawable/ic_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_list.xml b/app/src/main/res/drawable/ic_list.xml new file mode 100644 index 0000000..bae7be7 --- /dev/null +++ b/app/src/main/res/drawable/ic_list.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index f2d5e70..90c1d27 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -1,114 +1,139 @@ - + tools:ignore="ContentDescription"> - + android:layout_height="match_parent"> - - - + - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent"> + - + - + - + - + + + - + + + - \ No newline at end of file + + + + diff --git a/app/src/main/res/layout/item_demo_image.xml b/app/src/main/res/layout/item_demo_image.xml new file mode 100644 index 0000000..0eb8b33 --- /dev/null +++ b/app/src/main/res/layout/item_demo_image.xml @@ -0,0 +1,22 @@ + + + + + + + + + diff --git a/imagepicker/src/main/res/xml/provider_path.xml b/app/src/main/res/xml/provider_path.xml similarity index 100% rename from imagepicker/src/main/res/xml/provider_path.xml rename to app/src/main/res/xml/provider_path.xml diff --git a/build.gradle b/build.gradle index e61d8af..2bc9701 100644 --- a/build.gradle +++ b/build.gradle @@ -1,24 +1,22 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' - classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.20.0" - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files + classpath 'com.android.tools.build:gradle:7.3.1' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10' + //classpath "org.jfrog.buildinfo:build-info-extractor-gradle:4.20.0" } } allprojects { repositories { google() - jcenter() + mavenCentral() } } task clean(type: Delete) { delete rootProject.buildDir -} \ No newline at end of file +} diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index d956395..6eeef91 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Mon Feb 15 19:49:44 EET 2021 +#Tue Dec 13 18:15:08 EET 2022 distributionBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-bin.zip +zipStoreBase=GRADLE_USER_HOME diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle index 9d27d86..908e286 100644 --- a/imagepicker/build.gradle +++ b/imagepicker/build.gradle @@ -1,21 +1,20 @@ plugins { id 'com.android.library' + id 'kotlin-android' id 'maven-publish' - } def libraryGroupId = 'com.shz.imagepicker' def libraryArtifactId = 'imagepicker' -def libraryVersion = '1.0.1' +def libraryVersion = '2.0' android { - compileSdkVersion 31 - buildToolsVersion "30.0.3" + compileSdkVersion 33 defaultConfig { minSdkVersion 16 - targetSdkVersion 31 + targetSdkVersion 33 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" consumerProguardFiles "consumer-rules.pro" @@ -33,7 +32,9 @@ android { } dependencies { - implementation 'androidx.appcompat:appcompat:1.3.1' + implementation "androidx.appcompat:appcompat:1.5.1" + implementation "androidx.recyclerview:recyclerview:1.2.1" + implementation "androidx.exifinterface:exifinterface:1.3.5" } publishing { diff --git a/imagepicker/src/main/AndroidManifest.xml b/imagepicker/src/main/AndroidManifest.xml index 7fd6b56..1fe0a61 100644 --- a/imagepicker/src/main/AndroidManifest.xml +++ b/imagepicker/src/main/AndroidManifest.xml @@ -8,23 +8,19 @@ - + android:name=".activity.nativegallery.GalleryMultiPickerNativeActivity" + android:theme="@style/ImagePicker.Theme.Transparent" /> - - - - + android:name=".activity.nativegallery.GallerySinglePickerActivity" + android:theme="@style/ImagePicker.Theme.Transparent" /> + + + - - \ No newline at end of file + diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/CameraPickerActivity.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/CameraPickerActivity.java deleted file mode 100644 index c00bb9f..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/CameraPickerActivity.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; -import androidx.core.content.FileProvider; - -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.provider.MediaStore; -import android.util.Log; - -import com.shz.imagepicker.imagepicker.util.FileOrientationHandler; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; - -public class CameraPickerActivity extends Activity { - - public static ImagePickerCallback mCallback; - - private static final int PERMISSION_REQUEST_CAMERA = 54560; - private static final int IMAGE_REQUEST_CAMERA = 54561; - - private static final String FILE_PROVIDER_PREFIX = ".provider"; - - private String mFilename; - private FileOrientationHandler fileOrientationHandler = new FileOrientationHandler(); - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == PERMISSION_REQUEST_CAMERA) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startCameraPicker(); - } else { - finish(); - } - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_REQUEST_CAMERA) { - ArrayList files = new ArrayList<>(); - Uri uri = ImagePath.getCaptureImageResultUri(this, data, mFilename); - Uri uriFile = ImagePath.getNormalizedUri(this, uri); - File file = new File(uriFile.getPath()); - - try{ - fileOrientationHandler.rotateAndReWriteImageFile(file); - }catch (IOException e){ - Log.d(this.getClass().getSimpleName(),"Error rotation image "+e.getLocalizedMessage()); - } - - files.add(file); - mCallback.onImagesSelected(files); - } - finish(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - checkCameraPermission(); - } - - private void checkCameraPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions( - this, - new String[] { Manifest.permission.CAMERA }, - PERMISSION_REQUEST_CAMERA - ); - } else { - startCameraPicker(); - } - } - - private void startCameraPicker() { - Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); - mFilename = String.valueOf(System.nanoTime()); - Uri uri = ImagePath.getCaptureImageOutputUri(this, mFilename); - if (uri != null) { - File file = new File(uri.getPath()); - if (Build.VERSION.SDK_INT >= 24) { - cameraIntent.putExtra( - MediaStore.EXTRA_OUTPUT, - FileProvider.getUriForFile( - this, - this.getPackageName() + FILE_PROVIDER_PREFIX, - file - ) - ); - cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - } else { - cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri); - } - startActivityForResult(cameraIntent, IMAGE_REQUEST_CAMERA); - } - } - - - -} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GalleryMultiPickerActivity.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GalleryMultiPickerActivity.java deleted file mode 100644 index 26ca1f7..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GalleryMultiPickerActivity.java +++ /dev/null @@ -1,154 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import android.Manifest; -import android.app.Activity; -import android.content.ClipData; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.database.Cursor; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Parcelable; -import android.provider.MediaStore; -import android.util.Log; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import com.shz.imagepicker.imagepicker.util.FileOrientationHandler; - -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.util.ArrayList; - -public class GalleryMultiPickerActivity extends Activity { - - public static ImagePickerCallback mCallback; - - private static final int PERMISSION_REQUEST_READ_STORAGE = 54564; - private static final int IMAGE_REQUEST_GALLERY = 1; - - private static final String GALLERY_IMAGE_MIME = "image/jpeg"; - private FileOrientationHandler fileOrientationHandler = new FileOrientationHandler(); - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == PERMISSION_REQUEST_READ_STORAGE) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startGalleryPicker(); - } else { - finish(); - } - } - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - checkGalleryPermission(); - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_REQUEST_GALLERY) { - - - if (data != null) { - ArrayList files = new ArrayList<>(); - - - if (data.getExtras() != null) { - ArrayList fileUris = data.getExtras().getParcelableArrayList("selectedItems"); - - for (Parcelable uri : fileUris) { - if (uri instanceof Uri) { - files.add(new File(getRealPathFromURI((Uri) uri))); - } - - } - - - } - - if (data.getData() != null) { - Log.d("MultiPicker", "data: " + data.getData().toString()); - String filename = ImagePath.getImagePathFromInputStreamUri(this, data.getData()); - File file = new File(filename); - if (file.exists()) { - files.add(file); - } - } - - if (data.getClipData() != null) { - ClipData clipData = data.getClipData(); - - for (int i = 0; i < clipData.getItemCount(); i++) { - ClipData.Item item = clipData.getItemAt(i); - String filename = ImagePath.getImagePathFromInputStreamUri(this, item.getUri()); - File file = new File(filename); - if (file.exists()) { - files.add(file); - } - } - } - - - try { - for (File file : - files) { - fileOrientationHandler.rotateAndReWriteImageFile(file); - } - } catch (IOException e) { - Log.d(this.getClass().getSimpleName(), "Error rotating image " + e.getLocalizedMessage()); - } - - - mCallback.onImagesSelected(files); - } - } - finish(); - } - - public String getRealPathFromURI(Uri contentUri) { - String path = null; - String[] proj = {MediaStore.MediaColumns.DATA}; - Cursor cursor = getContentResolver().query(contentUri, proj, null, null, null); - if (cursor.moveToFirst()) { - int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); - path = cursor.getString(column_index); - } - cursor.close(); - return path; - } - - private void checkGalleryPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED - || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions( - this, - new String[]{Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, - PERMISSION_REQUEST_READ_STORAGE - ); - } else { - startGalleryPicker(); - } - } - - private void startGalleryPicker() { - Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) { - galleryIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true); - } - galleryIntent.putExtra("multi-pick", true); - - - //galleryIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, GALLERY_IMAGE_MIME); - startActivityForResult(galleryIntent, IMAGE_REQUEST_GALLERY); - - } -} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GallerySinglePickerActivity.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GallerySinglePickerActivity.java deleted file mode 100644 index 475312b..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/GallerySinglePickerActivity.java +++ /dev/null @@ -1,92 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import android.Manifest; -import android.app.Activity; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.provider.MediaStore; -import android.util.Log; - -import androidx.core.app.ActivityCompat; -import androidx.core.content.ContextCompat; - -import com.shz.imagepicker.imagepicker.util.FileOrientationHandler; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; - -public class GallerySinglePickerActivity extends Activity { - - public static ImagePickerCallback mCallback; - - private static final int PERMISSION_REQUEST_READ_STORAGE = 54562; - private static final int IMAGE_REQUEST_GALLERY = 54563; - - private static final String GALLERY_IMAGE_MIME = "image/jpeg"; - private FileOrientationHandler fileOrientationHandler = new FileOrientationHandler(); - - - @Override - public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == PERMISSION_REQUEST_READ_STORAGE) { - if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - startGalleryPicker(); - } else { - finish(); - } - } - } - - @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (resultCode == Activity.RESULT_OK && requestCode == IMAGE_REQUEST_GALLERY) { - if (data != null && data.getData() != null) { - ArrayList files = new ArrayList<>(); - String filename = ImagePath.getImagePathFromInputStreamUri(this, data.getData()); - Log.d("SinglePicker", "data: " + data.getData().toString()); - File file = new File(filename); - if (file.exists()) { - - try{ - fileOrientationHandler.rotateAndReWriteImageFile(file); - }catch (IOException e){ - Log.d(this.getClass().getSimpleName(),"Error rotation image "+e.getLocalizedMessage()); - } - - files.add(file); - mCallback.onImagesSelected(files); - } - } - } - finish(); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - checkGalleryPermission(); - } - - private void checkGalleryPermission() { - if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED - || ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_DENIED) { - ActivityCompat.requestPermissions( - this, - new String[] { Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE }, - PERMISSION_REQUEST_READ_STORAGE - ); - } else { - startGalleryPicker(); - } - } - - private void startGalleryPicker() { - Intent galleryIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI); - galleryIntent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, GALLERY_IMAGE_MIME); - startActivityForResult(galleryIntent, IMAGE_REQUEST_GALLERY); - } -} \ No newline at end of file diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePath.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePath.java deleted file mode 100644 index c64511b..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePath.java +++ /dev/null @@ -1,124 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import android.content.Context; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Environment; -import android.provider.MediaStore; - -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.Calendar; - -public class ImagePath { - - private static final String FILE_EXTENSION_JPEG = ".jpeg"; - private static final String FILE_EXTENSION_JPG = ".jpg"; - private static final String FILE_SCHEMA_CONTENT = "content:"; - - public static Uri getCaptureImageOutputUri(Context context, String filename) { - Uri outputUri = null; - File imageStore = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES); - if (imageStore != null) { - outputUri = Uri.fromFile(new File(imageStore.getPath(), filename + FILE_EXTENSION_JPEG)); - } - return outputUri; - } - - public static Uri getCaptureImageResultUri(Context context, Intent data, String filename) { - boolean isCamera = true; - if (data != null && data.getData() != null) { - String action = data.getAction(); - isCamera = action != null && action == MediaStore.ACTION_IMAGE_CAPTURE; - } - if (isCamera || data.getData() == null) { - return getCaptureImageOutputUri(context, filename); - } else { - return data.getData(); - } - } - - public static Uri getNormalizedUri(Context context, Uri uri) { - if (uri != null && uri.toString().contains(FILE_SCHEMA_CONTENT)) { - return Uri.fromFile(getPath(context, uri, MediaStore.Images.Media.DATA)); - } else { - return uri; - } - } - - public static String getImagePathFromInputStreamUri(Context context, Uri uri) { - InputStream inputStream = null; - String filePath = null; - - if (uri.getAuthority() != null) { - try { - inputStream = context.getContentResolver().openInputStream(uri); - File photoFile = createTempFileFrom(context, inputStream); - filePath = photoFile.getPath(); - } catch (FileNotFoundException ex) { - ex.printStackTrace(); - } catch (Exception ex) { - ex.printStackTrace(); - } finally { - try { - inputStream.close(); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - } - return filePath; - } - - private static File createTempFileFrom(Context context, InputStream inputStream) throws IOException { - File targetFile = null; - - if (inputStream != null) { - int read; - byte[] buffer = new byte[8 * 1024]; - - targetFile = createTempFile(context, null); - FileOutputStream outputStream = new FileOutputStream(targetFile); - - while (true) { - read = inputStream.read(buffer); - if (read == -1) { - break; - } - outputStream.write(buffer, 0, read); - } - outputStream.flush(); - - try { - outputStream.close(); - } catch (IOException ex) { - ex.printStackTrace(); - } - } - return targetFile; - } - - private static File createTempFile(Context context, String filePath) { - String tempFilename; - if (filePath == null) { - tempFilename = String.valueOf(Calendar.getInstance().getTimeInMillis()); - } else { - tempFilename = filePath; - } - return new File(context.getExternalCacheDir(), tempFilename + FILE_EXTENSION_JPG); - } - - private static File getPath(Context context, Uri uri, String column) { - String[] columns = { column }; - Cursor cursor = context.getContentResolver().query(uri, columns, null, null, null); - int columnIndex = cursor.getColumnIndexOrThrow(column); - cursor.moveToFirst(); - String path = cursor.getString(columnIndex); - cursor.close(); - return new File(path); - } -} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.java deleted file mode 100644 index c03ac0d..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.java +++ /dev/null @@ -1,97 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import android.app.Activity; -import android.content.Intent; - -public class ImagePicker { - - private static final String IMAGE_PICKER_DIALOG = "ImagePicker"; - - private Activity mActivity; - private ImagePickerCallback mCallback; - private boolean mIsUsingCamera = false; - private boolean mIsUsingGallery = false; - private boolean mIsUsingGalleryMultiSelect = false; - - private ImagePicker() { } - - public static class Builder { - private final ImagePicker mPicker; - - public Builder(Activity activity, ImagePickerCallback callback) { - mPicker = new ImagePicker(); - mPicker.mActivity = activity; - mPicker.mCallback = callback; - } - - public Builder useCamera(boolean isUsingCamera) { - mPicker.mIsUsingCamera = isUsingCamera; - return this; - } - - public Builder useGallery(boolean isUsingGallery) { - mPicker.mIsUsingGallery = isUsingGallery; - return this; - } - - public Builder useMultiSelection(boolean isUsingMultiSelection) { - mPicker.mIsUsingGalleryMultiSelect = isUsingMultiSelection; - return this; - } - - public ImagePicker build() { - return mPicker; - } - } - - public void start() { - if (mIsUsingCamera && !mIsUsingGallery) { - startCamera(); - } else if (mIsUsingGallery && !mIsUsingGalleryMultiSelect && !mIsUsingCamera) { - startGallerySingle(); - } else if (mIsUsingGallery && mIsUsingGalleryMultiSelect && !mIsUsingCamera) { - startGalleryMultiple(); - } else if (mIsUsingGallery && !mIsUsingGalleryMultiSelect && mIsUsingCamera) { - ImagePickerDialog dialog = new ImagePickerDialog(new ImagePickerDialog.ImagePickerDialogListener() { - @Override - public void onCamera() { - startCamera(); - } - - @Override - public void onGallery() { - startGallerySingle(); - } - }); - dialog.show(mActivity.getFragmentManager(), IMAGE_PICKER_DIALOG); - } else if (mIsUsingGallery && mIsUsingGalleryMultiSelect && mIsUsingCamera) { - ImagePickerDialog dialog = new ImagePickerDialog(new ImagePickerDialog.ImagePickerDialogListener() { - @Override - public void onCamera() { - startCamera(); - } - - @Override - public void onGallery() { - startGalleryMultiple(); - } - }); - dialog.show(mActivity.getFragmentManager(), IMAGE_PICKER_DIALOG); - } - } - - private void startCamera() { - CameraPickerActivity.mCallback = mCallback; - mActivity.startActivity(new Intent(mActivity, CameraPickerActivity.class)); - } - - private void startGallerySingle() { - GallerySinglePickerActivity.mCallback = mCallback; - mActivity.startActivity(new Intent(mActivity, GallerySinglePickerActivity.class)); - } - - private void startGalleryMultiple() { - GalleryMultiPickerActivity.mCallback = mCallback; - mActivity.startActivity(new Intent(mActivity, GalleryMultiPickerActivity.class)); - } -} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.kt new file mode 100644 index 0000000..7581a2e --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePicker.kt @@ -0,0 +1,267 @@ +@file:Suppress("unused", "NOTHING_TO_INLINE") + +package com.shz.imagepicker.imagepicker + +import android.content.Context +import android.util.Log +import android.widget.ImageView +import com.shz.imagepicker.imagepicker.exception.NothingToLaunchException +import com.shz.imagepicker.imagepicker.model.GalleryPicker +import com.shz.imagepicker.imagepicker.model.PickedResult +import java.io.File + +/** + * Main **ImagePicker** SDK class. + * Contains configuration models and main business logic of. + * + * @see ImagePicker.Builder + * + * @author ShiftHackZ (Dmitriy Moroz) + */ +class ImagePicker private constructor( + private val authority: String, + private val callback: ImagePickerCallback, + private val useCamera: Boolean, + private val useGallery: Boolean, + private val autoRotate: Boolean, + private val multipleSelection: Boolean, + private val minimumSelectionCount: Int, + private val maximumSelectionCount: Int, + private val galleryPicker: GalleryPicker, + private val loadDelegate: ImagePickerLoadDelegate, +) { + + /** + * Checks for access Camera or Media permissions, then + * launches **ImagePicker** according to defined configuration. + * + * Possible use cases: + * - When only Builder.useCamera(true) launches Camera Picker; + * - When only Builder.useGallery(true) launches Native or Custom Gallery Picker; + * - When both Builder.useCamera(true) and Builder.useGallery(true) interactive dialog will be launched; + * - Otherwise, will deliver [NothingToLaunchException] as [PickedResult.Error] to [ImagePickerCallback]. + * + * @param context it is recommended to pass Activity [Context] here. + * + * @see ImagePicker.Builder + */ + fun launch(context: Context) = with(ImagePickerLauncher(context)) { + when { + useCamera && !useGallery -> launchCameraPicker(authority, callback) + !useCamera && useGallery -> launchGalleryPicker( + callback = callback, + delegate = loadDelegate, + multipleSelection = multipleSelection, + autoRotate = autoRotate, + minimum = minimumSelectionCount, + maximum = maximumSelectionCount, + galleryPicker = galleryPicker, + ) + useCamera && useGallery -> launchDialog( + authority = authority, + callback = callback, + delegate = loadDelegate, + multipleSelection = multipleSelection, + autoRotate = autoRotate, + minimum = minimumSelectionCount, + maximum = maximumSelectionCount, + galleryPicker = galleryPicker, + ) + else -> deliverThrowable(callback, NothingToLaunchException()) + } + } + + /** + * Builder class that allows to configure and build [ImagePicker] instance. + * + * @param authority string of your FileProvider, defined in AndroidManifest.xml + * @param callback instance of [ImagePickerCallback] that will receive pick-operation result as [PickedResult] + * + * @see ImagePicker + */ + class Builder( + private val authority: String, + private val callback: ImagePickerCallback, + ) { + private var useCamera: Boolean = false + private var useGallery: Boolean = false + private var autoRotate: Boolean = false + private var multipleSelection: Boolean = false + private var minimumSelectionCount: Int = 1 + private var maximumSelectionCount: Int = Int.MAX_VALUE + private var galleryPicker: GalleryPicker = GalleryPicker.NATIVE + private var loadDelegate: ImagePickerLoadDelegate = defaultImagePickerLoadDelegate + + /** + * Must be called after desired parameters were passed to receive [ImagePicker] instance. + * + * @see ImagePicker + * + * @return instance of [ImagePicker] + * @throws IllegalArgumentException on incorrect minimumSelectionCount, maximumSelectionCount values. + */ + fun build(): ImagePicker { + require(minimumSelectionCount >= 1) { + "Parameter minimumSelectionCount must be bigger or equal 1" + } + require(maximumSelectionCount > minimumSelectionCount) { + "Parameter maximumSelectionCount must be bigger than minimumSelectionCount" + } + return ImagePicker( + authority = authority, + callback = callback, + useCamera = useCamera, + useGallery = useGallery, + autoRotate = autoRotate, + multipleSelection = multipleSelection, + minimumSelectionCount = minimumSelectionCount, + maximumSelectionCount = maximumSelectionCount, + galleryPicker = galleryPicker, + loadDelegate = loadDelegate, + ) + } + + /** + * Configures if Camera Picker is allowed to launch. + * + * @param useCamera if true will allow launch of Camera Picker. + * + * @return [ImagePicker.Builder] + */ + fun useCamera(useCamera: Boolean = true) = apply { + this.useCamera = useCamera + } + + /** + * Configures if Gallery Picker is allowed to launch. + * + * @param useGallery if true will allow launch of Gallery Picker. + * + * @return [ImagePicker.Builder] + */ + fun useGallery(useGallery: Boolean = true) = apply { + this.useGallery = useGallery + } + + /** + * Configures if Gallery Picker is allowed to auto rotate images with wrong EXIF rotation. + * If parameter is set to true, ImagePicker will re-write the original file with rotated one. + * + * @param autoRotate if true will allow auto rotate on any Gallery Picker. + * + * @return [ImagePicker.Builder] + */ + fun autoRotate(autoRotate: Boolean = false) = apply { + this.autoRotate = autoRotate + } + + /** + * Configures if multiple image selection is allowed in Gallery Picker. + * + * @param multipleSelection if true will allow multiple image selection in Gallery Picker. + * + * @return [ImagePicker.Builder] + */ + fun multipleSelection(multipleSelection: Boolean = true) = apply { + this.multipleSelection = multipleSelection + } + + /** + * Configures minimum amount of images that user will need to select. + * + * **Affects only** [GalleryPicker.CUSTOM]. + * + * @param minimumSelectionCount minimum allowed selection in [GalleryPicker.CUSTOM]. + * + * @return [ImagePicker.Builder] + */ + @Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") + fun minimumSelectionCount(minimumSelectionCount: Int) = apply { + this.minimumSelectionCount = minimumSelectionCount + } + + /** + * Configures maximum amount of images that user will need to select. + * + * **Affects only** [GalleryPicker.CUSTOM]. + * + * @param maximumSelectionCount maximum allowed selection in [GalleryPicker.CUSTOM]. + * + * @return [ImagePicker.Builder] + */ + @Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") + fun maximumSelectionCount(maximumSelectionCount: Int) = apply { + this.maximumSelectionCount = maximumSelectionCount + } + + /** + * Defines type of Gallery Picker, represented as [GalleryPicker]. + * + * Possible values: + * - [GalleryPicker.NATIVE] will launch Gallery Picker that is build in your Android ROM. + * - [GalleryPicker.CUSTOM] will launch Custom Gallery picker, provided by this library. + * + * @see GalleryPicker + * + * @param galleryPicker type of desired [GalleryPicker]. + * + * @return [ImagePicker.Builder] + */ + fun galleryPicker(galleryPicker: GalleryPicker) = apply { + this.galleryPicker = galleryPicker + } + + @Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") + fun loadDelegate(loadDelegate: ImagePickerLoadDelegate) = apply { + this.loadDelegate = loadDelegate + } + + @Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") + fun loadDelegate(loadDelegate: (ImageView, File) -> Unit) = apply { + this.loadDelegate = ImagePickerLoadDelegate { iv, file -> loadDelegate(iv, file) } + } + + companion object { + /** + * Must be called after desired parameters were passed to receive [ImagePicker] instance. + * + * @param authority string of your FileProvider, defined in AndroidManifest.xml; + * @param callback instance of [ImagePickerCallback] that will receive pick-operation result as [PickedResult]; + * @param block applicable kotlin builder block. + * + * @see ImagePicker + * + * @return instance of [ImagePicker] + */ + inline fun build( + authority: String, + callback: ImagePickerCallback, + block: Builder.() -> Unit, + ) = Builder(authority, callback) + .apply(block) + .build() + } + } + + companion object { + private const val TAG = "ShzImagePicker" + + internal inline fun deliverThrowable(clb: ImagePickerCallback, t: Throwable) { + t.also(::errorLog) + .let(PickedResult::Error) + .let(clb::onImagePickerResult) + } + + internal inline fun errorLog(t: Throwable) { + if (BuildConfig.DEBUG) Log.e(TAG, t.message.toString(), t) + } + + internal inline fun errorLog(message: String, t: Throwable? = null) { + if (BuildConfig.DEBUG) Log.e(TAG, message, t) + } + + internal inline fun debugLog(message: String) { + if (BuildConfig.DEBUG) Log.d(TAG, message) + } + } +} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.java deleted file mode 100644 index 9e73674..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import java.io.File; -import java.util.List; - -public interface ImagePickerCallback { - void onImagesSelected(List files); -} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.kt new file mode 100644 index 0000000..ac0ea3e --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerCallback.kt @@ -0,0 +1,26 @@ +package com.shz.imagepicker.imagepicker + +import com.shz.imagepicker.imagepicker.model.PickedResult + +/** + * Contract interface, that allows to receive image-pick result as [PickedResult]. + * + * @see PickedResult + */ +fun interface ImagePickerCallback { + + /** + * Returns result of image-pick operation. + * + * @see PickedResult + * + * @param result actual result of last image-pick operation. + * + * Possible values: + * - [PickedResult.Empty] if nothing was selected; + * - [PickedResult.Single] in case one image was selected; + * - [PickedResult.Multiple] in case multiple images were selected; + * - [PickedResult.Error] in case some error occurred. + */ + fun onImagePickerResult(result: PickedResult) +} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerDialog.java b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerDialog.java deleted file mode 100644 index 326d1dd..0000000 --- a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerDialog.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.shz.imagepicker.imagepicker; - -import android.annotation.SuppressLint; -import android.app.Dialog; -import android.app.DialogFragment; -import android.graphics.Color; -import android.graphics.drawable.ColorDrawable; -import android.os.Bundle; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.Window; -import android.view.WindowManager; -import android.widget.LinearLayout; - -@SuppressLint("ValidFragment") -public class ImagePickerDialog extends DialogFragment { - - public interface ImagePickerDialogListener { - void onCamera(); - void onGallery(); - } - - private ImagePickerDialogListener mListener; - - public ImagePickerDialog(ImagePickerDialogListener listener) { - super(); - this.mListener = listener; - } - - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - Dialog dialog = super.onCreateDialog(savedInstanceState); - dialog.getWindow().requestFeature(Window.FEATURE_NO_TITLE); - return dialog; - } - - @Override - public void onStart() { - super.onStart(); - Dialog dialog = getDialog(); - if (dialog != null) { - //dialog.getWindow().setLayout(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); - - WindowManager.LayoutParams params = dialog.getWindow().getAttributes(); - //params.horizontalMargin = 0.01f; - dialog.getWindow().setAttributes(params); - - dialog.getWindow().setBackgroundDrawable(new ColorDrawable(Color.WHITE)); - } - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - View rootView = inflater.inflate(R.layout.layout_image_picker, container, false); - ((LinearLayout) rootView.findViewById(R.id.btn_camera)).setOnClickListener(v -> { - mListener.onCamera(); - dismissAllowingStateLoss(); - }); - ((LinearLayout) rootView.findViewById(R.id.btn_gallery)).setOnClickListener(v -> { - mListener.onGallery(); - dismissAllowingStateLoss(); - }); - return rootView; - } -} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLauncher.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLauncher.kt new file mode 100644 index 0000000..74db798 --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLauncher.kt @@ -0,0 +1,123 @@ +package com.shz.imagepicker.imagepicker + +import android.content.Context +import android.content.Intent +import com.shz.imagepicker.imagepicker.activity.camera.CameraPickerActivity +import com.shz.imagepicker.imagepicker.activity.customgallery.GalleryPickerCustomActivity +import com.shz.imagepicker.imagepicker.activity.dialog.DialogLauncherActivity +import com.shz.imagepicker.imagepicker.activity.nativegallery.GalleryMultiPickerNativeActivity +import com.shz.imagepicker.imagepicker.activity.nativegallery.GallerySinglePickerActivity +import com.shz.imagepicker.imagepicker.core.ImagePickerActivity +import com.shz.imagepicker.imagepicker.model.GalleryPicker + +internal class ImagePickerLauncher(private val context: Context) { + + fun launchCameraPicker( + authority: String, + callback: ImagePickerCallback, + ) { + ImagePicker.debugLog("[Launcher] launchCameraPicker") + CameraPickerActivity.authority = authority + CameraPickerActivity.callback = callback + context.startActivity(Intent(context, CameraPickerActivity::class.java)) + } + + fun launchGalleryPicker( + callback: ImagePickerCallback, + delegate: ImagePickerLoadDelegate, + multipleSelection: Boolean, + autoRotate: Boolean, + minimum: Int, + maximum: Int, + galleryPicker: GalleryPicker, + ) { + ImagePicker.debugLog("[Launcher] launchGalleryPicker") + when (galleryPicker) { + GalleryPicker.NATIVE -> if (multipleSelection) { + launchMultipleSelectionGalleryNative(callback, autoRotate) + } else { + launchSingleSelectionGallery(callback, autoRotate) + } + GalleryPicker.CUSTOM -> { + launchMultipleSelectionGalleryCustom( + callback = callback, + delegate = delegate, + multipleSelection = multipleSelection, + autoRotate = autoRotate, + min = minimum, + max = maximum + ) + } + } + } + + fun launchDialog( + authority: String, + callback: ImagePickerCallback, + delegate: ImagePickerLoadDelegate, + multipleSelection: Boolean, + autoRotate: Boolean, + minimum: Int, + maximum: Int, + galleryPicker: GalleryPicker, + ) { + ImagePicker.debugLog("[Launcher] launchDialog") + DialogLauncherActivity.callback = callback + DialogLauncherActivity.loadDelegate = delegate + context.startActivity(Intent(context, DialogLauncherActivity::class.java).apply { + putExtra( + DialogLauncherActivity.BUNDLE_PAYLOAD, + DialogLauncherActivity.Payload( + authority, + multipleSelection, + autoRotate, + minimum, + maximum, + galleryPicker, + ) + ) + }) + } + + private fun launchSingleSelectionGallery( + callback: ImagePickerCallback, + autoRotate: Boolean, + ) { + ImagePicker.debugLog("[Launcher] launchSingleSelectionGallery") + GallerySinglePickerActivity.callback = callback + context.startActivity(Intent(context, GallerySinglePickerActivity::class.java).apply { + putExtra(ImagePickerActivity.BUNDLE_AUTO_ROTATE, autoRotate) + }) + } + + private fun launchMultipleSelectionGalleryNative( + callback: ImagePickerCallback, + autoRotate: Boolean, + ) { + ImagePicker.debugLog("[Launcher] launchMultipleSelectionGalleryNative") + GalleryMultiPickerNativeActivity.callback = callback + context.startActivity(Intent(context, GalleryMultiPickerNativeActivity::class.java).apply { + putExtra(ImagePickerActivity.BUNDLE_AUTO_ROTATE, autoRotate) + }) + } + + @Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") + private fun launchMultipleSelectionGalleryCustom( + callback: ImagePickerCallback, + delegate: ImagePickerLoadDelegate, + multipleSelection: Boolean, + autoRotate: Boolean, + min: Int, + max: Int, + ) { + ImagePicker.debugLog("[Launcher] launchMultipleSelectionGalleryCustom") + GalleryPickerCustomActivity.loadDelegate = delegate + GalleryPickerCustomActivity.callback = callback + context.startActivity(Intent(context, GalleryPickerCustomActivity::class.java).apply { + putExtra(ImagePickerActivity.BUNDLE_AUTO_ROTATE, autoRotate) + putExtra(GalleryPickerCustomActivity.BUNDLE_MULTI_SELECTION, multipleSelection) + putExtra(GalleryPickerCustomActivity.BUNDLE_MINIMUM, min) + putExtra(GalleryPickerCustomActivity.BUNDLE_MAXIMUM, max) + }) + } +} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLoadDelegate.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLoadDelegate.kt new file mode 100644 index 0000000..4167d29 --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/ImagePickerLoadDelegate.kt @@ -0,0 +1,13 @@ +package com.shz.imagepicker.imagepicker + +import android.graphics.BitmapFactory +import android.widget.ImageView +import java.io.File + +fun interface ImagePickerLoadDelegate { + fun load(imageView: ImageView, file: File) +} + +internal val defaultImagePickerLoadDelegate = ImagePickerLoadDelegate { iv, file -> + iv.setImageBitmap(BitmapFactory.decodeFile(file.path)) +} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/camera/CameraPickerActivity.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/camera/CameraPickerActivity.kt new file mode 100644 index 0000000..6122922 --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/camera/CameraPickerActivity.kt @@ -0,0 +1,69 @@ +package com.shz.imagepicker.imagepicker.activity.camera + +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.provider.MediaStore +import androidx.core.content.FileProvider +import com.shz.imagepicker.imagepicker.ImagePickerCallback +import com.shz.imagepicker.imagepicker.core.ImagePickerActivity +import com.shz.imagepicker.imagepicker.model.PickedImage +import com.shz.imagepicker.imagepicker.model.PickedResult +import com.shz.imagepicker.imagepicker.model.PickedSource +import com.shz.imagepicker.imagepicker.utils.checkCameraPermission +import com.shz.imagepicker.imagepicker.utils.getCaptureImageOutputUri +import com.shz.imagepicker.imagepicker.utils.getCaptureImageResultUri +import com.shz.imagepicker.imagepicker.utils.getNormalizedUri +import java.io.File + +internal class CameraPickerActivity : ImagePickerActivity() { + + override val requestCode: Int = 54500 + + private var filename: String = "" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + checkCameraPermission(requestCode, ::startPicker) + } + + override fun deliverResult(intent: Intent?) { + getCaptureImageResultUri(this, intent, filename) + ?.let { uri -> getNormalizedUri(this, uri) } + ?.path + ?.let(::File) + ?.let { file -> PickedImage(PickedSource.CAMERA, file) } + ?.let(PickedResult::Single) + ?.let(callback::onImagePickerResult) + } + + override fun startPicker() { + val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) + filename = System.nanoTime().toString() + val uri = getCaptureImageOutputUri(this, filename) + if (filename.isNotEmpty()) uri?.path?.let { path -> + val file = File(path) + if (Build.VERSION.SDK_INT >= 24) { + cameraIntent.putExtra( + MediaStore.EXTRA_OUTPUT, + FileProvider.getUriForFile( + this, + authority, + file, + ) + ) + cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION) + cameraIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + } else { + cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, uri) + } + launcher.launch(cameraIntent) + } + } + + companion object { + @JvmField + internal var callback: ImagePickerCallback = ImagePickerCallback { } + internal var authority: String = "" + } +} diff --git a/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/customgallery/GalleryPickerCustomActivity.kt b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/customgallery/GalleryPickerCustomActivity.kt new file mode 100644 index 0000000..274decf --- /dev/null +++ b/imagepicker/src/main/java/com/shz/imagepicker/imagepicker/activity/customgallery/GalleryPickerCustomActivity.kt @@ -0,0 +1,134 @@ +package com.shz.imagepicker.imagepicker.activity.customgallery + +import android.os.Build +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import android.widget.Button +import android.widget.FrameLayout +import android.widget.TextView +import androidx.core.view.isVisible +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.shz.imagepicker.imagepicker.* +import com.shz.imagepicker.imagepicker.activity.customgallery.adapter.multiple.GalleryImagesMultipleAdapter +import com.shz.imagepicker.imagepicker.activity.customgallery.adapter.single.GalleryImagesSingleAdapter +import com.shz.imagepicker.imagepicker.core.ImagePickerActivity +import com.shz.imagepicker.imagepicker.defaultImagePickerLoadDelegate +import com.shz.imagepicker.imagepicker.exception.FeatureNotSupportedException +import com.shz.imagepicker.imagepicker.model.PickedImage +import com.shz.imagepicker.imagepicker.model.PickedResult +import com.shz.imagepicker.imagepicker.model.PickedSource +import com.shz.imagepicker.imagepicker.utils.checkReadExternalStoragePermission +import com.shz.imagepicker.imagepicker.utils.getAllImages +import java.io.File + +@Deprecated("GalleryPicker.CUSTOM is deprecated from Android 13 (SDK 33)") +internal class GalleryPickerCustomActivity : ImagePickerActivity() { + + override val requestCode: Int = 54503 + + private var multipleSelection = false + private var max = Int.MAX_VALUE + private var min = 1 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (Build.VERSION.SDK_INT >= 33) { + val message = "This feature is not supported from Android 13 (SDK 33) due to Android restrictions\n" + + "Reference: https://developer.android.com/training/data-storage/shared/media#storage-permission" + ImagePicker.deliverThrowable( + callback, + FeatureNotSupportedException(message), + ) + finish() + } + setContentView(R.layout.image_picker_activity_gallery_custom) + + multipleSelection = intent.getBooleanExtra(BUNDLE_MULTI_SELECTION, false) + max = intent.getIntExtra(BUNDLE_MAXIMUM, 10) + min = intent.getIntExtra(BUNDLE_MINIMUM, 1) + + findViewById(R.id.bg_bottom_view)?.isVisible = multipleSelection + findViewById(R.id.fl_bottom_view)?.isVisible = multipleSelection + + findViewById