Merge pull request #1609 from oxen-io/feature/updating-cropping

Feature/updating cropping
This commit is contained in:
ThomasSession 2024-08-09 14:54:57 +10:00 committed by GitHub
commit 90cc704016
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 96 additions and 55 deletions

View File

@ -284,7 +284,7 @@ dependencies {
implementation 'com.pnikosis:materialish-progress:1.5'
implementation 'org.greenrobot:eventbus:3.0.0'
implementation 'pl.tajchert:waitingdots:0.1.0'
implementation 'com.theartofdev.edmodo:android-image-cropper:2.8.0'
implementation 'com.vanniktech:android-image-cropper:4.5.0'
implementation 'com.melnykov:floatingactionbutton:1.3.0'
implementation 'com.google.zxing:android-integration:3.1.0'
implementation 'mobi.upod:time-duration-picker:1.1.3'

View File

@ -6,13 +6,14 @@ import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import androidx.annotation.StringRes
import androidx.activity.result.ActivityResultLauncher
import androidx.core.content.ContextCompat
import com.theartofdev.edmodo.cropper.CropImage
import com.theartofdev.edmodo.cropper.CropImageView
import com.canhub.cropper.CropImageContractOptions
import com.canhub.cropper.CropImageOptions
import com.canhub.cropper.CropImageView
import network.loki.messenger.R
import org.session.libsession.utilities.getColorFromAttr
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
import org.session.libsignal.utilities.Log
import org.session.libsignal.utilities.NoExternalStorageException
@ -22,35 +23,49 @@ import java.io.File
import java.io.IOException
import java.util.LinkedList
object AvatarSelection {
class AvatarSelection(
private val activity: Activity,
private val onAvatarCropped: ActivityResultLauncher<CropImageContractOptions>,
private val onPickImage: ActivityResultLauncher<Intent>
) {
private val TAG: String = AvatarSelection::class.java.simpleName
const val REQUEST_CODE_CROP_IMAGE: Int = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE
const val REQUEST_CODE_AVATAR: Int = REQUEST_CODE_CROP_IMAGE + 1
private val bgColor by lazy { activity.getColorFromAttr(android.R.attr.colorPrimary) }
private val txtColor by lazy { activity.getColorFromAttr(android.R.attr.textColorPrimary) }
private val imageScrim by lazy { ContextCompat.getColor(activity, R.color.avatar_background) }
private val activityTitle by lazy { activity.getString(R.string.CropImageActivity_profile_avatar) }
/**
* Returns result on [.REQUEST_CODE_CROP_IMAGE]
*/
fun circularCropImage(
activity: Activity,
inputFile: Uri?,
outputFile: Uri?,
@StringRes title: Int
outputFile: Uri?
) {
CropImage.activity(inputFile)
.setGuidelines(CropImageView.Guidelines.ON)
.setAspectRatio(1, 1)
.setCropShape(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) CropImageView.CropShape.RECTANGLE else CropImageView.CropShape.OVAL)
.setOutputUri(outputFile)
.setAllowRotation(true)
.setAllowFlipping(true)
.setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background))
.setActivityTitle(activity.getString(title))
.start(activity)
}
fun getResultUri(data: Intent?): Uri {
return CropImage.getActivityResult(data).uri
onAvatarCropped.launch(
CropImageContractOptions(
uri = inputFile,
cropImageOptions = CropImageOptions(
guidelines = CropImageView.Guidelines.ON,
aspectRatioX = 1,
aspectRatioY = 1,
fixAspectRatio = true,
cropShape = CropImageView.CropShape.OVAL,
customOutputUri = outputFile,
allowRotation = true,
allowFlipping = true,
backgroundColor = imageScrim,
toolbarColor = bgColor,
activityBackgroundColor = bgColor,
toolbarTintColor = txtColor,
toolbarBackButtonColor = txtColor,
toolbarTitleColor = txtColor,
activityMenuIconColor = txtColor,
activityMenuTextColor = txtColor,
activityTitle = activityTitle
)
)
)
}
/**
@ -59,7 +74,6 @@ object AvatarSelection {
* @return Temporary capture file if created.
*/
fun startAvatarSelection(
activity: Activity,
includeClear: Boolean,
attemptToIncludeCamera: Boolean
): File? {
@ -80,7 +94,7 @@ object AvatarSelection {
}
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR)
onPickImage.launch(chooserIntent)
return captureFile
}

View File

@ -18,6 +18,7 @@ import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@ -34,6 +35,8 @@ import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import com.canhub.cropper.CropImage
import com.canhub.cropper.CropImageContract
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.awaitClose
@ -78,12 +81,12 @@ import org.thoughtcrime.securesms.ui.Cell
import org.thoughtcrime.securesms.ui.Divider
import org.thoughtcrime.securesms.ui.LargeItemButton
import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineButton
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
import org.thoughtcrime.securesms.ui.contentDescription
import org.thoughtcrime.securesms.ui.setThemedContent
import org.thoughtcrime.securesms.ui.theme.LocalDimensions
import org.thoughtcrime.securesms.ui.theme.dangerButtonColors
import org.thoughtcrime.securesms.util.BitmapDecodingException
import org.thoughtcrime.securesms.util.BitmapUtil
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
@ -109,6 +112,48 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
private val onAvatarCropped = registerForActivityResult(CropImageContract()) { result ->
when {
result.isSuccessful -> {
Log.i(TAG, result.getUriFilePath(this).toString())
lifecycleScope.launch(Dispatchers.IO) {
try {
val profilePictureToBeUploaded =
BitmapUtil.createScaledBytes(
this@SettingsActivity,
result.getUriFilePath(this@SettingsActivity).toString(),
ProfileMediaConstraints()
).bitmap
launch(Dispatchers.Main) {
updateProfilePicture(profilePictureToBeUploaded)
}
} catch (e: BitmapDecodingException) {
Log.e(TAG, e)
}
}
}
result is CropImage.CancelledResult -> {
Log.i(TAG, "Cropping image was cancelled by the user")
}
else -> {
Log.e(TAG, "Cropping image failed")
}
}
}
private val onPickImage = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
){ result ->
if (result.resultCode != Activity.RESULT_OK) return@registerForActivityResult
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
val inputFile: Uri? = result.data?.data ?: tempFile?.let(Uri::fromFile)
cropImage(inputFile, outputFile)
}
private val avatarSelection = AvatarSelection(this, onAvatarCropped, onPickImage)
companion object {
private const val SCROLL_STATE = "SCROLL_STATE"
}
@ -181,31 +226,6 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
}
}
@Deprecated("Deprecated in Java")
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode != Activity.RESULT_OK) return
when (requestCode) {
AvatarSelection.REQUEST_CODE_AVATAR -> {
val outputFile = Uri.fromFile(File(cacheDir, "cropped"))
val inputFile: Uri? = data?.data ?: tempFile?.let(Uri::fromFile)
AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar)
}
AvatarSelection.REQUEST_CODE_CROP_IMAGE -> {
lifecycleScope.launch(Dispatchers.IO) {
try {
val profilePictureToBeUploaded = BitmapUtil.createScaledBytes(this@SettingsActivity, AvatarSelection.getResultUri(data), ProfileMediaConstraints()).bitmap
launch(Dispatchers.Main) {
updateProfilePicture(profilePictureToBeUploaded)
}
} catch (e: BitmapDecodingException) {
Log.e(TAG, e)
}
}
}
}
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
@ -407,10 +427,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
Permissions.with(this)
.request(Manifest.permission.CAMERA)
.onAnyResult {
tempFile = AvatarSelection.startAvatarSelection(this, false, true)
tempFile = avatarSelection.startAvatarSelection( false, true)
}
.execute()
}
private fun cropImage(inputFile: Uri?, outputFile: Uri?){
avatarSelection.circularCropImage(
inputFile = inputFile,
outputFile = outputFile,
)
}
// endregion
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {