Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature for user to be able to pick image source #233

Merged
merged 11 commits into from
Oct 6, 2021
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- `Fixed` for any bug fixes.
- `Security` in case of vulnerabilities.

## [unreleased 3.3.6] - 05/10/21
### Added
- Added `PickImageContractOptions` to enable users specify image source [#226](https://github.com/CanHub/Android-Image-Cropper/issues/226)

## [3.3.5] - 07/09/21
### Fixed
- Set output uri ignored [#207](https://github.com/CanHub/Android-Image-Cropper/issues/207)
Expand Down
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,16 @@ class MainActivity {
}
)

//start picker to get image for cropping from only gallery and then use the image in
//cropping activity
cropImage.launch(
options {
setImagePickerContractOptions(
PickImageContractOptions(includeGallery = true, includeCamera = false)
)
}
)

// start cropping activity for pre-acquired image saved on the device and customize settings
cropImage.launch(
options(uri = imageUri) {
Expand Down
32 changes: 18 additions & 14 deletions cropper/src/main/java/com/canhub/cropper/CropImage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ import com.canhub.cropper.common.CommonVersionCheck
import com.canhub.cropper.common.CommonVersionCheck.isAtLeastQ29
import com.canhub.cropper.utils.getFilePathFromUri
import java.io.File
import java.util.ArrayList

/**
* Helper to simplify crop image work like starting pick-image acitvity and handling camera/gallery
Expand All @@ -63,6 +62,11 @@ object CropImage {
*/
const val CROP_IMAGE_EXTRA_OPTIONS = "CROP_IMAGE_EXTRA_OPTIONS"

/**
* The key used to pass options for the picker of image to crop to [CropImageActivity].
*/
const val PICK_IMAGE_SOURCE_OPTIONS = "PICK_IMAGE_SOURCE_OPTIONS"

/**
* The key used to pass crop image bundle data to [CropImageActivity].
*/
Expand Down Expand Up @@ -165,8 +169,7 @@ object CropImage {
return getPickImageChooserIntent(
context = context,
title = context.getString(R.string.pick_image_intent_chooser_title),
includeDocuments = false,
includeCamera = true
options = PickImageContractOptions()
)
}

Expand All @@ -185,22 +188,24 @@ object CropImage {
fun getPickImageChooserIntent(
context: Context,
title: CharSequence?,
includeDocuments: Boolean, // todo, remove this. Should always be false for image to crop.
includeCamera: Boolean,
options: PickImageContractOptions
): Intent {
val includeCamera = options.includeCamera
val includeGallery = options.includeGallery
val allIntents: MutableList<Intent> = ArrayList()
val packageManager = context.packageManager
// collect all camera intents if Camera permission is available
if (!isExplicitCameraPermissionRequired(context) && includeCamera) {
allIntents.addAll(getCameraIntents(context, packageManager))
}
allIntents.addAll(
getGalleryIntents(
packageManager,
Intent.ACTION_GET_CONTENT,
includeDocuments
if (includeGallery) {
allIntents.addAll(
getGalleryIntents(
packageManager,
Intent.ACTION_GET_CONTENT
)
)
)
}
// Create a chooser from the main intent
val chooserIntent = Intent.createChooser(allIntents.removeAt(allIntents.size - 1), title)
// Add all other intents
Expand Down Expand Up @@ -266,12 +271,11 @@ object CropImage {
// todo this need be public?
fun getGalleryIntents(
packageManager: PackageManager,
action: String?,
includeDocuments: Boolean,
action: String?
): List<Intent> {
val intents: MutableList<Intent> = ArrayList()
val galleryIntent = Intent(action)
galleryIntent.type = if (includeDocuments) "*/*" else "image/*"
galleryIntent.type = "image/*"
galleryIntent.addCategory(Intent.CATEGORY_OPENABLE)
var listGallery = packageManager.queryIntentActivities(galleryIntent, 0)
if (isAtLeastQ29() && listGallery.size > 2) {
Expand Down
71 changes: 39 additions & 32 deletions cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ open class CropImageActivity :
/**
* the options that were set for the crop image
*/
lateinit var options: CropImageOptions
lateinit var cropImageOptions: CropImageOptions
lateinit var pickImageOptions: PickImageContractOptions

/** The crop image view library widget used in the activity */
private var cropImageView: CropImageView? = null
Expand All @@ -52,7 +53,12 @@ open class CropImageActivity :
setCropImageView(binding.cropImageView)
val bundle = intent.getBundleExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE)
cropImageUri = bundle?.getParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE)
options = bundle?.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS) ?: CropImageOptions()
cropImageOptions =
bundle?.getParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS) ?: CropImageOptions()
pickImageOptions =
bundle?.getParcelable(CropImage.PICK_IMAGE_SOURCE_OPTIONS) ?: PickImageContractOptions(
includeCamera = true
)

if (savedInstanceState == null) {
if (cropImageUri == null || cropImageUri == Uri.EMPTY) {
Expand All @@ -63,7 +69,7 @@ open class CropImageActivity :
CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE
)
} else {
pickImage.launch(true)
pickImage.launch(pickImageOptions)
}
} else if (
cropImageUri?.let {
Expand All @@ -84,9 +90,10 @@ open class CropImageActivity :

supportActionBar?.let {
title =
if (options.activityTitle.isNotEmpty()) options.activityTitle else resources.getString(
R.string.crop_image_activity_title
)
if (cropImageOptions.activityTitle.isNotEmpty())
cropImageOptions.activityTitle
else
resources.getString(R.string.crop_image_activity_title)
it.setDisplayHomeAsUpEnabled(true)
}
}
Expand All @@ -106,37 +113,37 @@ open class CropImageActivity :
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.crop_image_menu, menu)

if (!options.allowRotation) {
if (!cropImageOptions.allowRotation) {
menu.removeItem(R.id.ic_rotate_left_24)
menu.removeItem(R.id.ic_rotate_right_24)
} else if (options.allowCounterRotation) {
} else if (cropImageOptions.allowCounterRotation) {
menu.findItem(R.id.ic_rotate_left_24).isVisible = true
}

if (!options.allowFlipping) menu.removeItem(R.id.ic_flip_24)
if (!cropImageOptions.allowFlipping) menu.removeItem(R.id.ic_flip_24)

if (options.cropMenuCropButtonTitle != null) {
menu.findItem(R.id.crop_image_menu_crop).title = options.cropMenuCropButtonTitle
if (cropImageOptions.cropMenuCropButtonTitle != null) {
menu.findItem(R.id.crop_image_menu_crop).title = cropImageOptions.cropMenuCropButtonTitle
}
var cropIcon: Drawable? = null
try {
if (options.cropMenuCropButtonIcon != 0) {
cropIcon = ContextCompat.getDrawable(this, options.cropMenuCropButtonIcon)
if (cropImageOptions.cropMenuCropButtonIcon != 0) {
cropIcon = ContextCompat.getDrawable(this, cropImageOptions.cropMenuCropButtonIcon)
menu.findItem(R.id.crop_image_menu_crop).icon = cropIcon
}
} catch (e: Exception) {
Log.w("AIC", "Failed to read menu crop drawable", e)
}
if (options.activityMenuIconColor != 0) {
updateMenuItemIconColor(menu, R.id.ic_rotate_left_24, options.activityMenuIconColor)
updateMenuItemIconColor(menu, R.id.ic_rotate_right_24, options.activityMenuIconColor)
updateMenuItemIconColor(menu, R.id.ic_flip_24, options.activityMenuIconColor)
if (cropImageOptions.activityMenuIconColor != 0) {
updateMenuItemIconColor(menu, R.id.ic_rotate_left_24, cropImageOptions.activityMenuIconColor)
updateMenuItemIconColor(menu, R.id.ic_rotate_right_24, cropImageOptions.activityMenuIconColor)
updateMenuItemIconColor(menu, R.id.ic_flip_24, cropImageOptions.activityMenuIconColor)

if (cropIcon != null) {
updateMenuItemIconColor(
menu,
R.id.crop_image_menu_crop,
options.activityMenuIconColor
cropImageOptions.activityMenuIconColor
)
}
}
Expand All @@ -146,8 +153,8 @@ open class CropImageActivity :
override fun onOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.crop_image_menu_crop -> cropImage()
R.id.ic_rotate_left_24 -> rotateImage(-options.rotationDegrees)
R.id.ic_rotate_right_24 -> rotateImage(options.rotationDegrees)
R.id.ic_rotate_left_24 -> rotateImage(-cropImageOptions.rotationDegrees)
R.id.ic_rotate_right_24 -> rotateImage(cropImageOptions.rotationDegrees)
R.id.ic_flip_24_horizontally -> cropImageView?.flipImageHorizontally()
R.id.ic_flip_24_vertically -> cropImageView?.flipImageVertically()
android.R.id.home -> setResultCancel()
Expand Down Expand Up @@ -205,17 +212,17 @@ open class CropImageActivity :
} else if (requestCode == CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE) {
// Irrespective of whether camera permission was given or not, we show the picker
// The picker will not add the camera intent if permission is not available
pickImage.launch(true)
pickImage.launch(pickImageOptions)
} else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

override fun onSetImageUriComplete(view: CropImageView, uri: Uri, error: Exception?) {
if (error == null) {
if (options.initialCropWindowRectangle != null)
cropImageView?.cropRect = options.initialCropWindowRectangle
if (cropImageOptions.initialCropWindowRectangle != null)
cropImageView?.cropRect = cropImageOptions.initialCropWindowRectangle

if (options.initialRotation > -1)
cropImageView?.rotatedDegrees = options.initialRotation
if (cropImageOptions.initialRotation > -1)
cropImageView?.rotatedDegrees = cropImageOptions.initialRotation
} else setResult(null, error, 1)
}

Expand All @@ -227,14 +234,14 @@ open class CropImageActivity :
* Execute crop image and save the result tou output uri.
*/
open fun cropImage() {
if (options.noOutputImage) setResult(null, null, 1)
if (cropImageOptions.noOutputImage) setResult(null, null, 1)
else cropImageView?.croppedImageAsync(
options.outputCompressFormat,
options.outputCompressQuality,
options.outputRequestWidth,
options.outputRequestHeight,
options.outputRequestSizeOptions,
options.customOutputUri,
cropImageOptions.outputCompressFormat,
cropImageOptions.outputCompressQuality,
cropImageOptions.outputRequestWidth,
cropImageOptions.outputRequestHeight,
cropImageOptions.outputRequestSizeOptions,
cropImageOptions.customOutputUri,
)
}

Expand Down
5 changes: 3 additions & 2 deletions cropper/src/main/java/com/canhub/cropper/CropImageContract.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@ class CropImageContract :
ActivityResultContract<CropImageContractOptions, CropImageView.CropResult>() {

override fun createIntent(context: Context, input: CropImageContractOptions): Intent {
input.options.validate()
input.cropImageOptions.validate()
return Intent(context, CropImageActivity::class.java).apply {
val bundle = Bundle()
bundle.putParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE, input.uri)
bundle.putParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS, input.options)
bundle.putParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS, input.cropImageOptions)
bundle.putParcelable(CropImage.PICK_IMAGE_SOURCE_OPTIONS, input.pickImageOptions)
putExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE, bundle)
}
}
Expand Down
Loading