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 ActivityResultContracts #145

Merged
merged 22 commits into from
Jul 1, 2021
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/)
- `Security` in case of vulnerabilities.

## [unreleased x.x.x] -
### Added
- `CropImageContract` and `PickImageContract`
- added dependency to `androidx.activity:activity-ktx:.2.3`

### Changed
- `CropImageActivity.onActivityResult` no longer receives any result. Override `onPickImageResult` instead.

### Deprecated
- deprecated old methods that depend on the deprecated `onActivityResult`. Use `CropImageContract` and `PickImageContract` instead.

## [3.1.3] - 10/06/21
### Fixed
Expand Down
52 changes: 21 additions & 31 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,43 +92,33 @@ override fun onCreate(savedInstanceState: Bundle?) {
```

### Start the default Activity
- Start `CropImageActivity` using builder pattern from your activity
- Register for activity result with `CropImageContract`
```kotlin
class MainActivity {
private fun startCrop() {
// start picker to get image for cropping and then use the image in cropping activity
CropImage
.activity()
.setGuidelines(CropImageView.Guidelines.ON)
.start(this)

// start cropping activity for pre-acquired image saved on the device
CropImage
.activity(imageUri)
.start(this)

// for fragment (DO NOT use `getActivity()`)
CropImage
.activity()
.start(requireContext(), this)
}
private val cropImage = registerForActivityResult(CropImageContract()) { result ->
if (result.isSuccessful) {
// use the returned uri
val uri = result.uriContent
} else {
// an error occured
}
}
}
```

- Override `onActivityResult` method in your activity to get crop result
- Launch the activity
```kotlin
class MainActivity {
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) {
val result = CropImage.getActivityResult(data)
if (resultCode == Activity.RESULT_OK) {
val resultUri: Uri? = result?.uriContent
val resultFilePath: String? = result?.getUriFilePath(requireContext())
} else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) {
val error = result!!.error
}
}
}
private fun startCrop() {
// start picker to get image for cropping and then use the image in cropping activity
cropImage.launch(options())

// start cropping activity for pre-acquired image saved on the device and customize settings
cropImage.launch(
options(uri = imageUri) {
setGuidelines(Guidelines.ON)
setOutputCompressFormat(CompressFormat.PNG)
}
)
connyduck marked this conversation as resolved.
Show resolved Hide resolved
}
```

Expand Down
17 changes: 17 additions & 0 deletions cropper/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -24,11 +24,21 @@ android {
buildFeatures {
viewBinding true
}
kotlinOptions {
jvmTarget = "1.8"
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}

dependencies {
api "androidx.appcompat:appcompat:$androidXAppCompatVersionCropper"

implementation "androidx.activity:activity-ktx:$androidXActivity"

implementation "androidx.exifinterface:exifinterface:$androidXExifVersion"
implementation "androidx.core:core-ktx:$androidXCoreKtxVersion"

Expand All @@ -37,4 +47,11 @@ dependencies {

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutinesVersion"

testImplementation "junit:junit:$junitVersion"
testImplementation "androidx.test.ext:junit:$androidXJunitVersion"
testImplementation "androidx.test:core:$androidXTestVersion"
testImplementation "androidx.test:runner:$androidXTestVersion"
debugImplementation "androidx.fragment:fragment-testing:$androidXFragmentVersion"
testImplementation "org.robolectric:robolectric:$robolectricVersion"
}
23 changes: 21 additions & 2 deletions cropper/src/main/java/com/canhub/cropper/CropImage.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.RequiresApi
import androidx.core.content.FileProvider
import androidx.fragment.app.Fragment
import com.canhub.cropper.CropImageOptions.Companion.DEGREES_360
import com.canhub.cropper.CropImageView.CropResult
import com.canhub.cropper.CropImageView.CropShape
import com.canhub.cropper.CropImageView.Guidelines
Expand Down Expand Up @@ -129,6 +130,7 @@ object CropImage {
*
* @param activity the activity to be used to start activity from
*/
@Deprecated("use the PickImageContract ActivityResultContract instead")
fun startPickImageActivity(activity: Activity) {
activity.startActivityForResult(
getPickImageChooserIntent(activity), PICK_IMAGE_CHOOSER_REQUEST_CODE
Expand All @@ -142,6 +144,7 @@ object CropImage {
* @param context The Fragments context. Use getContext()
* @param fragment The calling Fragment to start and return the image to
*/
@Deprecated("use the PickImageContract ActivityResultContract instead")
fun startPickImageActivity(context: Context, fragment: Fragment) {
fragment.startActivityForResult(
getPickImageChooserIntent(context), PICK_IMAGE_CHOOSER_REQUEST_CODE
Expand Down Expand Up @@ -465,6 +468,7 @@ object CropImage {
* @return builder for Crop Image Activity
*/
@JvmStatic
@Deprecated("use the CropImageContract ActivityResultContract instead")
fun activity(): ActivityBuilder {
return ActivityBuilder(null)
}
Expand All @@ -479,6 +483,7 @@ object CropImage {
* @return builder for Crop Image Activity
*/
@JvmStatic
@Deprecated("use the CropImageContract ActivityResultContract instead")
fun activity(uri: Uri?): ActivityBuilder {
return ActivityBuilder(uri)
}
Expand All @@ -499,6 +504,7 @@ object CropImage {
*
* @param mSource The image to crop source Android uri.
*/
@Deprecated("use the CropImageContract ActivityResultContract instead")
class ActivityBuilder(private val mSource: Uri?) {

/**
Expand Down Expand Up @@ -958,7 +964,7 @@ object CropImage {
* *Default: NONE - will read image exif data*
*/
fun setInitialRotation(initialRotation: Int): ActivityBuilder {
mOptions.initialRotation = (initialRotation + 360) % 360
mOptions.initialRotation = (initialRotation + DEGREES_360) % DEGREES_360
return this
}

Expand Down Expand Up @@ -995,7 +1001,7 @@ object CropImage {
* *Default: 90*
*/
fun setRotationDegrees(rotationDegrees: Int): ActivityBuilder {
mOptions.rotationDegrees = (rotationDegrees + 360) % 360
mOptions.rotationDegrees = (rotationDegrees + DEGREES_360) % DEGREES_360
return this
}

Expand Down Expand Up @@ -1101,4 +1107,17 @@ object CropImage {
}
}
}

object CancelledResult : CropImageView.CropResult(
originalBitmap = null,
originalUri = null,
bitmap = null,
uriContent = null,
error = Exception("cropping has been cancelled by the user"),
cropPoints = floatArrayOf(),
cropRect = null,
wholeImageRect = null,
rotation = 0,
sampleSize = 0
)
}
50 changes: 23 additions & 27 deletions cropper/src/main/java/com/canhub/cropper/CropImageActivity.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.canhub.cropper

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.graphics.Bitmap
Expand Down Expand Up @@ -49,6 +48,8 @@ open class CropImageActivity :
private var cropImageView: CropImageView? = null
private lateinit var binding: CropImageActivityBinding

private val pickImage = registerForActivityResult(PickImageContract(), ::onPickImageResult)

public override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -68,7 +69,7 @@ open class CropImageActivity :
CropImage.CAMERA_CAPTURE_PERMISSIONS_REQUEST_CODE
)
} else {
CropImage.startPickImageActivity(this)
pickImage.launch(true)
}
} else if (
cropImageUri?.let {
Expand Down Expand Up @@ -163,30 +164,25 @@ open class CropImageActivity :
setResultCancel()
}

@SuppressLint("NewApi")
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
// handle result of pick image chooser
if (requestCode == CropImage.PICK_IMAGE_CHOOSER_REQUEST_CODE) {
if (resultCode == RESULT_CANCELED) setResultCancel()
if (resultCode == RESULT_OK) {
cropImageUri = CropImage.getPickImageResultUriContent(this, data)
// For API >= 23 we need to check specifically that we have permissions to read external
// storage.
if (cropImageUri?.let {
CropImage.isReadExternalStoragePermissionsRequired(this, it)
} == true &&
CommonVersionCheck.isAtLeastM23()
) {
// request permissions and handle the result in onRequestPermissionsResult()
requestPermissions(
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE
)
} else {
// no permissions required or already grunted, can start crop image activity
cropImageView?.setImageUriAsync(cropImageUri)
}
protected open fun onPickImageResult(resultUri: Uri?) {
if (resultUri == null) setResultCancel()
if (resultUri != null) {
cropImageUri = resultUri
// For API >= 23 we need to check specifically that we have permissions to read external
// storage.
if (cropImageUri?.let {
CropImage.isReadExternalStoragePermissionsRequired(this, it)
} == true &&
CommonVersionCheck.isAtLeastM23()
) {
// request permissions and handle the result in onRequestPermissionsResult()
requestPermissions(
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
CropImage.PICK_IMAGE_PERMISSIONS_REQUEST_CODE
)
} else {
// no permissions required or already granted, can start crop image activity
cropImageView?.setImageUriAsync(cropImageUri)
}
}
}
Expand All @@ -212,7 +208,7 @@ 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
CropImage.startPickImageActivity(this)
pickImage.launch(true)
} else super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}

Expand Down
40 changes: 40 additions & 0 deletions cropper/src/main/java/com/canhub/cropper/CropImageContract.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.canhub.cropper

import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.result.contract.ActivityResultContract
import com.canhub.cropper.CropImage.getActivityResult

/**
* An ActivityResultContract to start an activity that allows the user to crop an image.
* The activity can be heavily customized by the input CropImageContractOptions.
* If you do not provide an uri in the input the user will be asked to pick an image before cropping.
*/

class CropImageContract :
ActivityResultContract<CropImageContractOptions, CropImageView.CropResult>() {

override fun createIntent(context: Context, input: CropImageContractOptions): Intent {
input.options.validate()
return Intent().apply {
val bundle = Bundle()
bundle.putParcelable(CropImage.CROP_IMAGE_EXTRA_SOURCE, input.uri)
bundle.putParcelable(CropImage.CROP_IMAGE_EXTRA_OPTIONS, input.options)
putExtra(CropImage.CROP_IMAGE_EXTRA_BUNDLE, bundle)
}
}

override fun parseResult(
resultCode: Int,
intent: Intent?
): CropImageView.CropResult {
val result = getActivityResult(intent)
return if (result == null || resultCode == Activity.RESULT_CANCELED) {
CropImage.CancelledResult
} else {
result
}
}
}
Loading