diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/imagepicker/build.gradle b/imagepicker/build.gradle
index 39aa0d0d..fdcbec77 100644
--- a/imagepicker/build.gradle
+++ b/imagepicker/build.gradle
@@ -6,11 +6,11 @@ plugins {
apply from: "../ktlint.gradle"
android {
- compileSdkVersion 30
+ compileSdkVersion 33
defaultConfig {
minSdkVersion 19
- targetSdkVersion 30
+ targetSdkVersion 33
versionCode 16
versionName "2.1"
diff --git a/imagepicker/src/main/AndroidManifest.xml b/imagepicker/src/main/AndroidManifest.xml
index 2a14f96a..2c6a94fd 100644
--- a/imagepicker/src/main/AndroidManifest.xml
+++ b/imagepicker/src/main/AndroidManifest.xml
@@ -1,8 +1,17 @@
+
+
+
+
+
+
+
+
+
+
-
-
-
-
@@ -38,4 +44,4 @@
-
+
\ No newline at end of file
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePicker.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePicker.kt
index fddfbe4c..724d3a52 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePicker.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePicker.kt
@@ -306,7 +306,8 @@ open class ImagePicker {
}
},
dismissListener
- )
+ ,activity)
+
} else {
onResult(createIntent())
}
@@ -328,7 +329,7 @@ open class ImagePicker {
}
},
dismissListener
- )
+ ,activity )
}
/**
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerActivity.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerActivity.kt
index 6f0e95bf..38b5336d 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerActivity.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/ImagePickerActivity.kt
@@ -1,12 +1,6 @@
package com.github.dhaval2404.imagepicker
import android.app.Activity
-import android.content.Context
-import android.content.Intent
-import android.net.Uri
-import android.os.Bundle
-import android.util.Log
-import androidx.appcompat.app.AppCompatActivity
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import com.github.dhaval2404.imagepicker.provider.CameraProvider
import com.github.dhaval2404.imagepicker.provider.CompressionProvider
@@ -21,17 +15,25 @@ import com.github.dhaval2404.imagepicker.util.FileUriUtils
* @version 1.0
* @since 04 January 2019
*/
+import android.Manifest
+import android.content.Intent
+import android.content.Context
+import android.content.pm.PackageManager
+import android.net.Uri
+import android.os.Build
+import android.os.Bundle
+import android.util.Log
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
+/*import com.example.imagepicker.ImagePicker
+import com.example.imagepicker.utils.FileUriUtils*/
+
class ImagePickerActivity : AppCompatActivity() {
companion object {
private const val TAG = "image_picker"
-
- internal fun getCancelledIntent(context: Context): Intent {
- val intent = Intent()
- val message = context.getString(R.string.error_task_cancelled)
- intent.putExtra(ImagePicker.EXTRA_ERROR, message)
- return intent
- }
+ private const val REQUEST_CODE_STORAGE_PERMISSION = 100
}
private var mGalleryProvider: GalleryProvider? = null
@@ -44,19 +46,7 @@ class ImagePickerActivity : AppCompatActivity() {
loadBundle(savedInstanceState)
}
- /**
- * Save all appropriate activity state.
- */
- public override fun onSaveInstanceState(outState: Bundle) {
- mCameraProvider?.onSaveInstanceState(outState)
- mCropProvider.onSaveInstanceState(outState)
- super.onSaveInstanceState(outState)
- }
-
- /**
- * Parse Intent Bundle and initialize variables
- */
- private fun loadBundle(savedInstanceState: Bundle?) {
+ /* private fun loadBundle(savedInstanceState: Bundle?) {
// Create Crop Provider
mCropProvider = CropProvider(this)
mCropProvider.onRestoreInstanceState(savedInstanceState)
@@ -64,46 +54,106 @@ class ImagePickerActivity : AppCompatActivity() {
// Create Compression Provider
mCompressionProvider = CompressionProvider(this)
- // Retrieve Image Provider
+ // Check and request permissions based on Android version
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { // Marshmallow and above
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { // Android 13 (API level 33) and above
+ // Request permission only for accessing photos
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_MEDIA_IMAGES), REQUEST_CODE_STORAGE_PERMISSION)
+ } else {
+ initImageProvider(savedInstanceState)
+ }
+ } else {
+ // Request permission for accessing photos and videos
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE), REQUEST_CODE_STORAGE_PERMISSION)
+ } else {
+ initImageProvider(savedInstanceState)
+ }
+ }
+ } else {
+ // For devices below Marshmallow, permissions are granted at installation
+ initImageProvider(savedInstanceState)
+ }
+ }*/
+
+ private fun loadBundle(savedInstanceState: Bundle?) {
+
+ mCropProvider = CropProvider(this)
+ mCropProvider.onRestoreInstanceState(savedInstanceState)
+ mCompressionProvider = CompressionProvider(this)
+
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+ if (Build.VERSION.SDK_INT >=33) {
+ // Android 13 and above photos only
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_MEDIA_IMAGES) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.READ_MEDIA_IMAGES),
+ REQUEST_CODE_STORAGE_PERMISSION
+ )
+ } else {
+ initImageProvider(savedInstanceState)
+ }
+ } else {
+ // Android 12 and below
+ if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ this,
+ arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
+ REQUEST_CODE_STORAGE_PERMISSION
+ )
+ } else {
+ initImageProvider(savedInstanceState)
+ }
+ }
+ } else {
+ initImageProvider(savedInstanceState)
+ }
+ }
+
+
+
+
+
+ private fun initImageProvider(savedInstanceState: Bundle?) {
val provider: ImageProvider? =
intent?.getSerializableExtra(ImagePicker.EXTRA_IMAGE_PROVIDER) as ImageProvider?
- // Create Gallery/Camera Provider
when (provider) {
ImageProvider.GALLERY -> {
mGalleryProvider = GalleryProvider(this)
- // Pick Gallery Image
savedInstanceState ?: mGalleryProvider?.startIntent()
}
ImageProvider.CAMERA -> {
mCameraProvider = CameraProvider(this)
- mCameraProvider?.onRestoreInstanceState(savedInstanceState)
- // Pick Camera Image
savedInstanceState ?: mCameraProvider?.startIntent()
}
else -> {
- // Something went Wrong! This case should never happen
- Log.e(TAG, "Image provider can not be null")
+ Log.e(TAG, "Image provider cannot be null")
setError(getString(R.string.error_task_cancelled))
}
}
}
- /**
- * Dispatch incoming result to the correct provider.
- */
- override fun onRequestPermissionsResult(
- requestCode: Int,
- permissions: Array,
- grantResults: IntArray
- ) {
+ override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
- mCameraProvider?.onRequestPermissionsResult(requestCode)
+ if (requestCode == REQUEST_CODE_STORAGE_PERMISSION) {
+ if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
+ initImageProvider(null)
+ } else {
+ setError("Permission denied")
+ }
+ }
+ }
+
+ override fun onSaveInstanceState(outState: Bundle) {
+ mCameraProvider?.onSaveInstanceState(outState)
+ mCropProvider.onSaveInstanceState(outState)
+ super.onSaveInstanceState(outState)
}
- /**
- * Dispatch incoming result to the correct provider.
- */
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
mCameraProvider?.onActivityResult(requestCode, resultCode, data)
@@ -111,18 +161,10 @@ class ImagePickerActivity : AppCompatActivity() {
mCropProvider.onActivityResult(requestCode, resultCode, data)
}
- /**
- * Handle Activity Back Press
- */
override fun onBackPressed() {
setResultCancel()
}
- /**
- * {@link CameraProvider} and {@link GalleryProvider} Result will be available here.
- *
- * @param uri Capture/Gallery image Uri
- */
fun setImage(uri: Uri) {
when {
mCropProvider.isCropEnabled() -> mCropProvider.startIntent(uri)
@@ -131,16 +173,7 @@ class ImagePickerActivity : AppCompatActivity() {
}
}
- /**
- * {@link CropProviders} Result will be available here.
- *
- * Check if compression is enable/required. If yes then start compression else return result.
- *
- * @param uri Crop image uri
- */
fun setCropImage(uri: Uri) {
- // Delete Camera file after crop. Else there will be two image for the same action.
- // In case of Gallery Provider, we will get original image path, so we will not delete that.
mCameraProvider?.delete()
if (mCompressionProvider.isCompressionRequired(uri)) {
@@ -150,29 +183,12 @@ class ImagePickerActivity : AppCompatActivity() {
}
}
- /**
- * {@link CompressionProvider} Result will be available here.
- *
- * @param uri Compressed image Uri
- */
fun setCompressedImage(uri: Uri) {
- // This is the case when Crop is not enabled
-
- // Delete Camera file after crop. Else there will be two image for the same action.
- // In case of Gallery Provider, we will get original image path, so we will not delete that.
mCameraProvider?.delete()
-
- // If crop file is not null, Delete it after crop
mCropProvider.delete()
-
setResult(uri)
}
- /**
- * Set Result, Image is successfully capture/picked/cropped/compressed.
- *
- * @param uri final image Uri
- */
private fun setResult(uri: Uri) {
val intent = Intent()
intent.data = uri
@@ -181,23 +197,22 @@ class ImagePickerActivity : AppCompatActivity() {
finish()
}
- /**
- * User has cancelled the task
- */
fun setResultCancel() {
setResult(Activity.RESULT_CANCELED, getCancelledIntent(this))
finish()
}
- /**
- * Error occurred while processing image
- *
- * @param message Error Message
- */
fun setError(message: String) {
val intent = Intent()
intent.putExtra(ImagePicker.EXTRA_ERROR, message)
setResult(ImagePicker.RESULT_ERROR, intent)
finish()
}
-}
+
+ internal fun getCancelledIntent(context: Context): Intent {
+ val intent = Intent()
+ val message = context.getString(R.string.error_task_cancelled)
+ intent.putExtra(ImagePicker.EXTRA_ERROR, message)
+ return intent
+ }
+}
\ No newline at end of file
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
index 58736424..4584198c 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/BaseProvider.kt
@@ -13,7 +13,7 @@ import java.io.File
* @version 1.0
* @since 04 January 2019
*/
-abstract class BaseProvider(protected val activity: ImagePickerActivity) :
+abstract class BaseProvider(protected open val activity: ImagePickerActivity) :
ContextWrapper(activity) {
fun getFileDir(path: String?): File {
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
index 2451fcf9..b5088fd9 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/provider/GalleryProvider.kt
@@ -1,9 +1,14 @@
package com.github.dhaval2404.imagepicker.provider
+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 androidx.core.app.ActivityCompat
+import androidx.core.content.ContextCompat
import com.github.dhaval2404.imagepicker.ImagePicker
import com.github.dhaval2404.imagepicker.ImagePickerActivity
import com.github.dhaval2404.imagepicker.R
@@ -16,6 +21,89 @@ import com.github.dhaval2404.imagepicker.util.IntentUtils
* @version 1.0
* @since 04 January 2019
*/
+
+
+class GalleryProvider(override val activity: ImagePickerActivity) : BaseProvider(activity) {
+
+ companion object {
+ private const val GALLERY_INTENT_REQ_CODE = 4261
+ private const val PERMISSION_REQUEST_CODE = 200
+ }
+
+ private val mimeTypes: Array = activity.intent.extras?.getStringArray(ImagePicker.EXTRA_MIME_TYPES) ?: emptyArray()
+
+ fun startIntent() {
+ if (isPermissionGranted()) {
+ startGalleryIntent()
+ } else {
+ requestPermissions()
+ }
+ }
+
+ private fun isPermissionGranted(): Boolean {
+ val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_IMAGES
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ }
+ return ContextCompat.checkSelfPermission(activity, permission) == PackageManager.PERMISSION_GRANTED
+ }
+
+ private fun requestPermissions() {
+ val permission = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ Manifest.permission.READ_MEDIA_IMAGES
+ } else {
+ Manifest.permission.READ_EXTERNAL_STORAGE
+ }
+ ActivityCompat.requestPermissions(activity, arrayOf(permission), PERMISSION_REQUEST_CODE)
+ }
+
+ private fun startGalleryIntent() {
+ val galleryIntent = IntentUtils.getGalleryIntent(activity, mimeTypes)
+ activity.startActivityForResult(galleryIntent, GALLERY_INTENT_REQ_CODE)
+ }
+
+ fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
+ if (requestCode == GALLERY_INTENT_REQ_CODE && resultCode == Activity.RESULT_OK) {
+ handleResult(data)
+ } else {
+ setResultCancel()
+ }
+ }
+
+ private fun handleResult(data: Intent?) {
+ val uri = data?.data
+ if (uri != null) {
+ takePersistableUriPermission(uri)
+ activity.setImage(uri)
+ } else {
+ setError(R.string.error_failed_pick_gallery_image)
+ }
+ }
+
+ private fun takePersistableUriPermission(uri: Uri) {
+ val contentResolver = activity.contentResolver
+ contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ }
+}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+/*
class GalleryProvider(activity: ImagePickerActivity) :
BaseProvider(activity) {
@@ -33,28 +121,34 @@ class GalleryProvider(activity: ImagePickerActivity) :
mimeTypes = bundle.getStringArray(ImagePicker.EXTRA_MIME_TYPES) ?: emptyArray()
}
- /**
+ */
+/**
* Start Gallery Capture Intent
- */
+ *//*
+
fun startIntent() {
startGalleryIntent()
}
- /**
+ */
+/**
* Start Gallery Intent
- */
+ *//*
+
private fun startGalleryIntent() {
val galleryIntent = IntentUtils.getGalleryIntent(activity, mimeTypes)
activity.startActivityForResult(galleryIntent, GALLERY_INTENT_REQ_CODE)
}
- /**
+ */
+/**
* Handle Gallery Intent Activity Result
*
* @param requestCode It must be {@link GalleryProvider#GALLERY_INTENT_REQ_CODE}
* @param resultCode For success it should be {@link Activity#RESULT_OK}
* @param data Result Intent
- */
+ *//*
+
fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == GALLERY_INTENT_REQ_CODE) {
if (resultCode == Activity.RESULT_OK) {
@@ -65,9 +159,11 @@ class GalleryProvider(activity: ImagePickerActivity) :
}
}
- /**
+ */
+/**
* This method will be called when final result fot this provider is enabled.
- */
+ *//*
+
private fun handleResult(data: Intent?) {
val uri = data?.data
if (uri != null) {
@@ -78,11 +174,14 @@ class GalleryProvider(activity: ImagePickerActivity) :
}
}
- /**
+ */
+/**
* Take a persistable URI permission grant that has been offered. Once
* taken, the permission grant will be remembered across device reboots.
- */
+ *//*
+
private fun takePersistableUriPermission(uri: Uri) {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
+*/
diff --git a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/DialogHelper.kt b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/DialogHelper.kt
index 416a3fa6..bc27c5af 100644
--- a/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/DialogHelper.kt
+++ b/imagepicker/src/main/kotlin/com/github/dhaval2404/imagepicker/util/DialogHelper.kt
@@ -1,9 +1,14 @@
package com.github.dhaval2404.imagepicker.util
+import android.app.Activity
import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Build
import android.view.LayoutInflater
import android.view.View
+import android.widget.Toast
import androidx.appcompat.app.AlertDialog
+import androidx.core.app.ActivityCompat
import com.github.dhaval2404.imagepicker.R
import com.github.dhaval2404.imagepicker.constant.ImageProvider
import com.github.dhaval2404.imagepicker.listener.DismissListener
@@ -18,14 +23,18 @@ import com.github.dhaval2404.imagepicker.listener.ResultListener
*/
internal object DialogHelper {
+ const val PERMISSION_CAMERA_REQUEST_CODE = 101
+ const val PERMISSION_GALLERY_REQUEST_CODE = 102
+
/**
- * Show Image Provide Picker Dialog. This will streamline the code to pick/capture image
+ * Show Image Provider Picker Dialog. This streamlines the code to pick/capture images
*
*/
fun showChooseAppDialog(
context: Context,
listener: ResultListener,
- dismissListener: DismissListener?
+ dismissListener: DismissListener?,
+ activity: Activity // Add an Activity reference to handle permissions
) {
val layoutInflater = LayoutInflater.from(context)
val customView = layoutInflater.inflate(R.layout.dialog_choose_app, null)
@@ -46,14 +55,36 @@ internal object DialogHelper {
// Handle Camera option click
customView.findViewById(R.id.lytCameraPick).setOnClickListener {
- listener.onResult(ImageProvider.CAMERA)
- dialog.dismiss()
+ if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(android.Manifest.permission.CAMERA),
+ PERMISSION_CAMERA_REQUEST_CODE
+ )
+ } else {
+ listener.onResult(ImageProvider.CAMERA)
+ dialog.dismiss()
+ }
}
// Handle Gallery option click
customView.findViewById(R.id.lytGalleryPick).setOnClickListener {
- listener.onResult(ImageProvider.GALLERY)
- dialog.dismiss()
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+ // For Android 13 and above, modify this as necessary
+ Toast.makeText(context, "Limited gallery access in Android 13+", Toast.LENGTH_LONG).show()
+ } else {
+ // Request permission before returning the gallery option
+ if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
+ ActivityCompat.requestPermissions(
+ activity,
+ arrayOf(android.Manifest.permission.READ_EXTERNAL_STORAGE),
+ PERMISSION_GALLERY_REQUEST_CODE
+ )
+ } else {
+ listener.onResult(ImageProvider.GALLERY)
+ dialog.dismiss()
+ }
+ }
}
}
-}
+}
\ No newline at end of file
diff --git a/sample/build.gradle b/sample/build.gradle
index 694b73ec..28511046 100644
--- a/sample/build.gradle
+++ b/sample/build.gradle
@@ -7,11 +7,11 @@ plugins {
apply from: "../ktlint.gradle"
android {
- compileSdkVersion 30
+ compileSdkVersion 33
defaultConfig {
applicationId "com.github.dhaval2404.imagepicker.sample"
minSdkVersion 19
- targetSdkVersion 30
+ targetSdkVersion 33
versionCode 16
versionName "2.1"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/main/AndroidManifest.xml
index 57062e84..5f20de62 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/main/AndroidManifest.xml
@@ -14,7 +14,8 @@
+ android:theme="@style/AppTheme.NoActionBar"
+ android:exported="true">
@@ -25,7 +26,8 @@
android:name=".SampleActivity"
android:label="@string/app_name"
android:theme="@style/AppTheme.NoActionBar"
- tools:ignore="MissingClass">
+ tools:ignore="MissingClass"
+ android:exported="true">