From c4e7f9fe30ed8ff655383317d40a70bed00932cb Mon Sep 17 00:00:00 2001 From: Daniel Gultsch Date: Sun, 30 Mar 2025 10:14:39 +0200 Subject: [PATCH] bump image cropper --- build.gradle | 2 +- .../persistance/FileBackend.java | 43 +++--- ...hooseAccountForProfilePictureActivity.java | 5 +- .../conversations/ui/EditAccountActivity.java | 4 + ...ublishGroupChatProfilePictureActivity.java | 50 ++++--- .../ui/PublishProfilePictureActivity.java | 135 ++++++++---------- src/main/res/values/strings.xml | 2 +- 7 files changed, 115 insertions(+), 126 deletions(-) diff --git a/build.gradle b/build.gradle index 6500d21ed67c240b10f0006b88c9c195f78d7895..7a375cdbbd7c8db18222adf538988b330b050aa1 100644 --- a/build.gradle +++ b/build.gradle @@ -58,7 +58,7 @@ dependencies { conversationsPlaystoreImplementation("com.android.installreferrer:installreferrer:2.2") quicksyPlaystoreImplementation 'com.google.android.gms:play-services-auth-api-phone:18.2.0' implementation 'com.github.open-keychain.open-keychain:openpgp-api:v5.7.1' - implementation("com.github.CanHub:Android-Image-Cropper:2.0.0") + implementation("com.vanniktech:android-image-cropper:4.6.0") implementation "androidx.sharetarget:sharetarget:1.2.0" implementation 'androidx.appcompat:appcompat:1.7.0' diff --git a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java index 179d8b7e7c632ca26bb1e2a03f67200e9f2bb283..c7f4e0d24555bf573bf7833b3cef04fd798f1f0c 100644 --- a/src/main/java/eu/siacs/conversations/persistance/FileBackend.java +++ b/src/main/java/eu/siacs/conversations/persistance/FileBackend.java @@ -1395,34 +1395,36 @@ public class FileBackend { return Uri.fromFile(getAvatarFile(avatar)); } - public Bitmap cropCenterSquare(Uri image, int size) { + public Bitmap cropCenterSquare(final Uri image, final int size) { if (image == null) { return null; } - InputStream is = null; + final BitmapFactory.Options options = new BitmapFactory.Options(); try { - BitmapFactory.Options options = new BitmapFactory.Options(); options.inSampleSize = calcSampleSize(image, size); - is = mXmppConnectionService.getContentResolver().openInputStream(image); + } catch (final IOException | SecurityException e) { + Log.d(Config.LOGTAG, "unable to calculate sample size for " + image, e); + return null; + } + try (final InputStream is = + mXmppConnectionService.getContentResolver().openInputStream(image)) { if (is == null) { return null; } - Bitmap input = BitmapFactory.decodeStream(is, null, options); - if (input == null) { + final var originalBitmap = BitmapFactory.decodeStream(is, null, options); + if (originalBitmap == null) { return null; } else { - input = rotate(input, getRotation(image)); - return cropCenterSquare(input, size); + final var bitmap = rotate(originalBitmap, getRotation(image)); + return cropCenterSquare(bitmap, size); } - } catch (FileNotFoundException | SecurityException e) { - Log.d(Config.LOGTAG, "unable to open file " + image.toString(), e); + } catch (final SecurityException | IOException e) { + Log.d(Config.LOGTAG, "unable to open file " + image, e); return null; - } finally { - close(is); } } - public Bitmap cropCenter(Uri image, int newHeight, int newWidth) { + public Bitmap cropCenter(final Uri image, final int newHeight, final int newWidth) { if (image == null) { return null; } @@ -1458,7 +1460,7 @@ public class FileBackend { return dest; } catch (SecurityException e) { return null; // android 6.0 with revoked permissions for example - } catch (FileNotFoundException e) { + } catch (IOException e) { return null; } finally { close(is); @@ -1486,15 +1488,14 @@ public class FileBackend { return output; } - private int calcSampleSize(Uri image, int size) - throws FileNotFoundException, SecurityException { + private int calcSampleSize(final Uri image, int size) throws IOException, SecurityException { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; - final InputStream inputStream = - mXmppConnectionService.getContentResolver().openInputStream(image); - BitmapFactory.decodeStream(inputStream, null, options); - close(inputStream); - return calcSampleSize(options, size); + try (final InputStream inputStream = + mXmppConnectionService.getContentResolver().openInputStream(image)) { + BitmapFactory.decodeStream(inputStream, null, options); + return calcSampleSize(options, size); + } } public void updateFileParams(final Message message) { diff --git a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java index ff60e6419b2abde89bbc53b6fce4417bb90bef7b..767109a36675111e2a89460d0a05736eab715c80 100644 --- a/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/ChooseAccountForProfilePictureActivity.java @@ -24,7 +24,7 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { } @Override - protected void onCreate(Bundle savedInstanceState) { + protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); final ActivityManageAccountsBinding binding = DataBindingUtil.setContentView(this, R.layout.activity_manage_accounts); @@ -64,11 +64,12 @@ public class ChooseAccountForProfilePictureActivity extends XmppActivity { } } - private void goToProfilePictureActivity(Account account) { + private void goToProfilePictureActivity(final Account account) { final Intent startIntent = getIntent(); final Uri uri = startIntent == null ? null : startIntent.getData(); if (uri != null) { Intent intent = new Intent(this, PublishProfilePictureActivity.class); + intent.setAction(Intent.ACTION_ATTACH_DATA); intent.putExtra(EXTRA_ACCOUNT, account.getJid().asBareJid().toString()); intent.setData(uri); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); diff --git a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java index 78b962be510c8535ca26ea6536fe12715a71ec26..728af3cd4a752eb19d04340dfc42661b147dc8e9 100644 --- a/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/EditAccountActivity.java @@ -1202,6 +1202,10 @@ public class EditAccountActivity extends OmemoActivity this.binding.accountPassword.setFocusableInTouchMode(editPassword); this.binding.accountPassword.setCursorVisible(editPassword); this.binding.accountPassword.setEnabled(editPassword); + if (!editPassword && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + this.binding.accountJid.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + this.binding.accountPassword.setImportantForAutofill(View.IMPORTANT_FOR_AUTOFILL_NO); + } if (!mInitMode) { this.binding.avater.setVisibility(View.VISIBLE); diff --git a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java index 85a2b8b727909c8aa584f948857d7a9df1a97c04..ab06c24b471bde00c07712bac67e13f34a582987 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishGroupChatProfilePictureActivity.java @@ -29,18 +29,17 @@ package eu.siacs.conversations.ui; -import static eu.siacs.conversations.ui.PublishProfilePictureActivity.REQUEST_CHOOSE_PICTURE; - -import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; -import com.canhub.cropper.CropImage; +import com.canhub.cropper.CropImageContract; +import com.canhub.cropper.CropImageContractOptions; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; import eu.siacs.conversations.databinding.ActivityPublishProfilePictureBinding; @@ -55,6 +54,15 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity private Conversation conversation; private Uri uri; + final ActivityResultLauncher cropImage = + registerForActivityResult( + new CropImageContract(), + cropResult -> { + if (cropResult.isSuccessful()) { + onAvatarPicked(cropResult.getUriContent()); + } + }); + @Override protected void refreshUiReal() {} @@ -94,8 +102,8 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity configureActionBar(getSupportActionBar()); this.binding.cancelButton.setOnClickListener((v) -> this.finish()); this.binding.secondaryHint.setVisibility(View.GONE); - this.binding.accountImage.setOnClickListener( - (v) -> PublishProfilePictureActivity.chooseAvatar(this)); + this.binding.accountImage.setOnClickListener((v) -> pickAvatar()); + final var intent = getIntent(); final var uuid = intent == null ? null : intent.getStringExtra("uuid"); if (uuid != null) { @@ -111,26 +119,16 @@ public class PublishGroupChatProfilePictureActivity extends XmppActivity xmppConnectionService.publishMucAvatar(conversation, uri, this); } - @Override - public void onActivityResult(final int requestCode, final int resultCode, final Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { - final CropImage.ActivityResult result = CropImage.getActivityResult(data); - if (resultCode == RESULT_OK) { - this.uri = result == null ? null : result.getUri(); - if (xmppConnectionServiceBound) { - reloadAvatar(); - } - } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) { - final var error = result == null ? null : result.getError(); - if (error != null) { - Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(); - } - } - } else if (requestCode == REQUEST_CHOOSE_PICTURE) { - if (resultCode == RESULT_OK) { - PublishProfilePictureActivity.cropUri(this, data.getData()); - } + public void pickAvatar() { + this.cropImage.launch( + new CropImageContractOptions( + null, PublishProfilePictureActivity.getCropImageOptions())); + } + + private void onAvatarPicked(final Uri uri) { + this.uri = uri; + if (xmppConnectionServiceBound) { + reloadAvatar(); } } diff --git a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java index ebc8225d80ee0140657b660a0e1f202e25346e44..7901a39bffc853a02cbf5cd60c47ed860b5eeefb 100644 --- a/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java +++ b/src/main/java/eu/siacs/conversations/ui/PublishProfilePictureActivity.java @@ -1,10 +1,8 @@ package eu.siacs.conversations.ui; -import android.app.Activity; import android.content.Intent; import android.graphics.Bitmap; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.util.Log; import android.view.Menu; @@ -12,10 +10,13 @@ import android.view.MenuItem; import android.view.View; import android.view.View.OnLongClickListener; import android.widget.Toast; +import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.databinding.DataBindingUtil; -import com.canhub.cropper.CropImage; +import com.canhub.cropper.CropImageContract; +import com.canhub.cropper.CropImageContractOptions; +import com.canhub.cropper.CropImageOptions; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import eu.siacs.conversations.Config; import eu.siacs.conversations.R; @@ -24,7 +25,6 @@ import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.services.XmppConnectionService; import eu.siacs.conversations.ui.interfaces.OnAvatarPublication; import eu.siacs.conversations.utils.PhoneHelper; -import java.util.concurrent.atomic.AtomicBoolean; public class PublishProfilePictureActivity extends XmppActivity implements XmppConnectionService.OnAccountUpdate, OnAvatarPublication { @@ -37,7 +37,6 @@ public class PublishProfilePictureActivity extends XmppActivity private Account account; private boolean support = false; private boolean publishing = false; - private final AtomicBoolean handledExternalUri = new AtomicBoolean(false); private final OnLongClickListener backToDefaultListener = new OnLongClickListener() { @@ -50,6 +49,15 @@ public class PublishProfilePictureActivity extends XmppActivity }; private boolean mInitialAccountSetup; + final ActivityResultLauncher cropImage = + registerForActivityResult( + new CropImageContract(), + cropResult -> { + if (cropResult.isSuccessful()) { + onAvatarPicked(cropResult.getUriContent()); + } + }); + @Override public void onAvatarPublicationSucceeded() { runOnUiThread( @@ -85,6 +93,7 @@ public class PublishProfilePictureActivity extends XmppActivity @Override public void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); this.binding = @@ -123,12 +132,10 @@ public class PublishProfilePictureActivity extends XmppActivity } finish(); }); - this.binding.accountImage.setOnClickListener(v -> chooseAvatar(this)); + this.binding.accountImage.setOnClickListener(v -> pickAvatar(null)); this.defaultUri = PhoneHelper.getProfilePictureUri(getApplicationContext()); if (savedInstanceState != null) { this.avatarUri = savedInstanceState.getParcelable("uri"); - this.handledExternalUri.set( - savedInstanceState.getBoolean("handle_external_uri", false)); } } @@ -171,47 +178,32 @@ public class PublishProfilePictureActivity extends XmppActivity if (this.avatarUri != null) { outState.putParcelable("uri", this.avatarUri); } - outState.putBoolean("handle_external_uri", handledExternalUri.get()); super.onSaveInstanceState(outState); } - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - if (requestCode == CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE) { - CropImage.ActivityResult result = CropImage.getActivityResult(data); - if (resultCode == RESULT_OK) { - this.avatarUri = result.getUri(); - if (xmppConnectionServiceBound) { - loadImageIntoPreview(this.avatarUri); - } - } else if (resultCode == CropImage.CROP_IMAGE_ACTIVITY_RESULT_ERROR_CODE) { - Exception error = result.getError(); - if (error != null) { - Toast.makeText(this, error.getMessage(), Toast.LENGTH_SHORT).show(); - } - } - } else if (requestCode == REQUEST_CHOOSE_PICTURE) { - if (resultCode == RESULT_OK) { - cropUri(this, data.getData()); - } - } + public void pickAvatar(final Uri image) { + this.cropImage.launch(new CropImageContractOptions(image, getCropImageOptions())); } - public static void chooseAvatar(final Activity activity) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { - final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("image/*"); - activity.startActivityForResult( - Intent.createChooser( - intent, activity.getString(R.string.attach_choose_picture)), - REQUEST_CHOOSE_PICTURE); + public static CropImageOptions getCropImageOptions() { + final var cropImageOptions = new CropImageOptions(); + cropImageOptions.aspectRatioX = 1; + cropImageOptions.aspectRatioY = 1; + cropImageOptions.fixAspectRatio = true; + cropImageOptions.outputCompressFormat = Bitmap.CompressFormat.PNG; + cropImageOptions.imageSourceIncludeCamera = false; + cropImageOptions.minCropResultHeight = Config.AVATAR_SIZE; + cropImageOptions.minCropResultWidth = Config.AVATAR_SIZE; + return cropImageOptions; + } + + private void onAvatarPicked(final Uri uri) { + Log.d(Config.LOGTAG, "onAvatarPicked(" + uri + ")"); + this.avatarUri = uri; + if (xmppConnectionServiceBound) { + loadImageIntoPreview(uri); } else { - CropImage.activity() - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(1, 1) - .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) - .start(activity); + Log.d(Config.LOGTAG, "not ready during avatarPick"); } } @@ -243,61 +235,54 @@ public class PublishProfilePictureActivity extends XmppActivity public void onStart() { super.onStart(); final Intent intent = getIntent(); - this.mInitialAccountSetup = intent != null && intent.getBooleanExtra("setup", false); - - final Uri uri = intent != null ? intent.getData() : null; + if (intent == null) { + return; + } + this.mInitialAccountSetup = intent.getBooleanExtra("setup", false); - if (uri != null && handledExternalUri.compareAndSet(false, true)) { - cropUri(this, uri); + final var data = intent.getData(); + final var account = intent.getStringExtra(EXTRA_ACCOUNT); + if (Intent.ACTION_ATTACH_DATA.equals(intent.getAction()) + && data != null + && account != null) { + pickAvatar(data); + final var replacement = new Intent(Intent.ACTION_MAIN); + replacement.putExtra(EXTRA_ACCOUNT, account); + setIntent(replacement); return; } if (this.mInitialAccountSetup) { this.binding.cancelButton.setText(R.string.skip); } - configureActionBar( - getSupportActionBar(), !this.mInitialAccountSetup && !handledExternalUri.get()); - } - - public static void cropUri(final Activity activity, final Uri uri) { - CropImage.activity(uri) - .setOutputCompressFormat(Bitmap.CompressFormat.PNG) - .setAspectRatio(1, 1) - .setMinCropResultSize(Config.AVATAR_SIZE, Config.AVATAR_SIZE) - .start(activity); + configureActionBar(getSupportActionBar(), !this.mInitialAccountSetup); } protected void loadImageIntoPreview(final Uri uri) { - - Bitmap bm = null; + Log.d(Config.LOGTAG, "loadImageIntoPreview(" + uri + ")"); + final Bitmap bitmap; if (uri == null) { - bm = + bitmap = avatarService() .get( account, (int) getResources().getDimension(R.dimen.publish_avatar_size)); } else { - try { - bm = - xmppConnectionService - .getFileBackend() - .cropCenterSquare( - uri, - (int) - getResources() - .getDimension(R.dimen.publish_avatar_size)); - } catch (final Exception e) { - Log.d(Config.LOGTAG, "unable to load bitmap into image view", e); - } + bitmap = + xmppConnectionService + .getFileBackend() + .cropCenterSquare( + uri, + (int) getResources().getDimension(R.dimen.publish_avatar_size)); } - if (bm == null) { + if (bitmap == null) { togglePublishButton(false, R.string.publish); this.binding.hintOrWarning.setVisibility(View.VISIBLE); this.binding.hintOrWarning.setText(R.string.error_publish_avatar_converting); return; } - this.binding.accountImage.setImageBitmap(bm); + this.binding.accountImage.setImageBitmap(bitmap); if (support) { togglePublishButton(uri != null, R.string.publish); this.binding.hintOrWarning.setVisibility(View.INVISIBLE); diff --git a/src/main/res/values/strings.xml b/src/main/res/values/strings.xml index c8cfeb49493bbee6995daab50f06e1bfcbdc398a..cdc5d5b89a0846acd70d3a045a35838b38c8e0ac 100644 --- a/src/main/res/values/strings.xml +++ b/src/main/res/values/strings.xml @@ -843,7 +843,7 @@ e-book Original (uncompressed) Open with… - Conversations profile picture + Avatar Choose account Restore backup Restore