mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-20 21:38:26 +00:00
Merge pull request #1609 from oxen-io/feature/updating-cropping
Feature/updating cropping
This commit is contained in:
commit
90cc704016
@ -284,7 +284,7 @@ dependencies {
|
|||||||
implementation 'com.pnikosis:materialish-progress:1.5'
|
implementation 'com.pnikosis:materialish-progress:1.5'
|
||||||
implementation 'org.greenrobot:eventbus:3.0.0'
|
implementation 'org.greenrobot:eventbus:3.0.0'
|
||||||
implementation 'pl.tajchert:waitingdots:0.1.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.melnykov:floatingactionbutton:1.3.0'
|
||||||
implementation 'com.google.zxing:android-integration:3.1.0'
|
implementation 'com.google.zxing:android-integration:3.1.0'
|
||||||
implementation 'mobi.upod:time-duration-picker:1.1.3'
|
implementation 'mobi.upod:time-duration-picker:1.1.3'
|
||||||
|
@ -6,13 +6,14 @@ import android.content.Context
|
|||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
|
||||||
import android.provider.MediaStore
|
import android.provider.MediaStore
|
||||||
import androidx.annotation.StringRes
|
import androidx.activity.result.ActivityResultLauncher
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.theartofdev.edmodo.cropper.CropImage
|
import com.canhub.cropper.CropImageContractOptions
|
||||||
import com.theartofdev.edmodo.cropper.CropImageView
|
import com.canhub.cropper.CropImageOptions
|
||||||
|
import com.canhub.cropper.CropImageView
|
||||||
import network.loki.messenger.R
|
import network.loki.messenger.R
|
||||||
|
import org.session.libsession.utilities.getColorFromAttr
|
||||||
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
import org.session.libsignal.utilities.ExternalStorageUtil.getImageDir
|
||||||
import org.session.libsignal.utilities.Log
|
import org.session.libsignal.utilities.Log
|
||||||
import org.session.libsignal.utilities.NoExternalStorageException
|
import org.session.libsignal.utilities.NoExternalStorageException
|
||||||
@ -22,35 +23,49 @@ import java.io.File
|
|||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedList
|
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
|
private val TAG: String = AvatarSelection::class.java.simpleName
|
||||||
|
|
||||||
const val REQUEST_CODE_CROP_IMAGE: Int = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE
|
private val bgColor by lazy { activity.getColorFromAttr(android.R.attr.colorPrimary) }
|
||||||
const val REQUEST_CODE_AVATAR: Int = REQUEST_CODE_CROP_IMAGE + 1
|
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]
|
* Returns result on [.REQUEST_CODE_CROP_IMAGE]
|
||||||
*/
|
*/
|
||||||
fun circularCropImage(
|
fun circularCropImage(
|
||||||
activity: Activity,
|
|
||||||
inputFile: Uri?,
|
inputFile: Uri?,
|
||||||
outputFile: Uri?,
|
outputFile: Uri?
|
||||||
@StringRes title: Int
|
|
||||||
) {
|
) {
|
||||||
CropImage.activity(inputFile)
|
onAvatarCropped.launch(
|
||||||
.setGuidelines(CropImageView.Guidelines.ON)
|
CropImageContractOptions(
|
||||||
.setAspectRatio(1, 1)
|
uri = inputFile,
|
||||||
.setCropShape(if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) CropImageView.CropShape.RECTANGLE else CropImageView.CropShape.OVAL)
|
cropImageOptions = CropImageOptions(
|
||||||
.setOutputUri(outputFile)
|
guidelines = CropImageView.Guidelines.ON,
|
||||||
.setAllowRotation(true)
|
aspectRatioX = 1,
|
||||||
.setAllowFlipping(true)
|
aspectRatioY = 1,
|
||||||
.setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background))
|
fixAspectRatio = true,
|
||||||
.setActivityTitle(activity.getString(title))
|
cropShape = CropImageView.CropShape.OVAL,
|
||||||
.start(activity)
|
customOutputUri = outputFile,
|
||||||
}
|
allowRotation = true,
|
||||||
|
allowFlipping = true,
|
||||||
fun getResultUri(data: Intent?): Uri {
|
backgroundColor = imageScrim,
|
||||||
return CropImage.getActivityResult(data).uri
|
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.
|
* @return Temporary capture file if created.
|
||||||
*/
|
*/
|
||||||
fun startAvatarSelection(
|
fun startAvatarSelection(
|
||||||
activity: Activity,
|
|
||||||
includeClear: Boolean,
|
includeClear: Boolean,
|
||||||
attemptToIncludeCamera: Boolean
|
attemptToIncludeCamera: Boolean
|
||||||
): File? {
|
): File? {
|
||||||
@ -80,7 +94,7 @@ object AvatarSelection {
|
|||||||
}
|
}
|
||||||
|
|
||||||
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
val chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear)
|
||||||
activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR)
|
onPickImage.launch(chooserIntent)
|
||||||
return captureFile
|
return captureFile
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ import android.view.View
|
|||||||
import android.view.inputmethod.EditorInfo
|
import android.view.inputmethod.EditorInfo
|
||||||
import android.view.inputmethod.InputMethodManager
|
import android.view.inputmethod.InputMethodManager
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
@ -34,6 +35,8 @@ import androidx.core.view.isInvisible
|
|||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager
|
||||||
|
import com.canhub.cropper.CropImage
|
||||||
|
import com.canhub.cropper.CropImageContract
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
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.Divider
|
||||||
import org.thoughtcrime.securesms.ui.LargeItemButton
|
import org.thoughtcrime.securesms.ui.LargeItemButton
|
||||||
import org.thoughtcrime.securesms.ui.LargeItemButtonWithDrawable
|
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.PrimaryOutlineButton
|
||||||
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
|
import org.thoughtcrime.securesms.ui.components.PrimaryOutlineCopyButton
|
||||||
import org.thoughtcrime.securesms.ui.contentDescription
|
import org.thoughtcrime.securesms.ui.contentDescription
|
||||||
import org.thoughtcrime.securesms.ui.setThemedContent
|
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.BitmapDecodingException
|
||||||
import org.thoughtcrime.securesms.util.BitmapUtil
|
import org.thoughtcrime.securesms.util.BitmapUtil
|
||||||
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
|
||||||
@ -109,6 +112,48 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
|
|
||||||
private val hexEncodedPublicKey: String get() = TextSecurePreferences.getLocalNumber(this)!!
|
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 {
|
companion object {
|
||||||
private const val SCROLL_STATE = "SCROLL_STATE"
|
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) {
|
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
|
||||||
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
Permissions.onRequestPermissionsResult(this, requestCode, permissions, grantResults)
|
||||||
@ -407,10 +427,17 @@ class SettingsActivity : PassphraseRequiredActionBarActivity() {
|
|||||||
Permissions.with(this)
|
Permissions.with(this)
|
||||||
.request(Manifest.permission.CAMERA)
|
.request(Manifest.permission.CAMERA)
|
||||||
.onAnyResult {
|
.onAnyResult {
|
||||||
tempFile = AvatarSelection.startAvatarSelection(this, false, true)
|
tempFile = avatarSelection.startAvatarSelection( false, true)
|
||||||
}
|
}
|
||||||
.execute()
|
.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun cropImage(inputFile: Uri?, outputFile: Uri?){
|
||||||
|
avatarSelection.circularCropImage(
|
||||||
|
inputFile = inputFile,
|
||||||
|
outputFile = outputFile,
|
||||||
|
)
|
||||||
|
}
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
private inner class DisplayNameEditActionModeCallback: ActionMode.Callback {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user