Replace Avatar Cropper.

This commit is contained in:
Alan Evans 2019-03-18 16:09:57 -03:00 committed by Greyson Parrelli
parent 0cb2404735
commit 286b64274c
7 changed files with 141 additions and 74 deletions

View File

@ -403,7 +403,8 @@
android:theme="@style/TextSecure.ScribbleTheme" android:theme="@style/TextSecure.ScribbleTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/> android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
<activity android:name="com.soundcloud.android.crop.CropImageActivity" /> <activity android:name="com.theartofdev.edmodo.cropper.CropImageActivity"
android:theme="@style/TextSecure.DarkTheme"/>
<activity android:name=".CreateProfileActivity" <activity android:name=".CreateProfileActivity"
android:theme="@style/TextSecure.LightRegistrationTheme" android:theme="@style/TextSecure.LightRegistrationTheme"

View File

@ -106,7 +106,7 @@ dependencies {
compile 'com.pnikosis:materialish-progress:1.5' compile 'com.pnikosis:materialish-progress:1.5'
compile 'org.greenrobot:eventbus:3.0.0' compile 'org.greenrobot:eventbus:3.0.0'
compile 'pl.tajchert:waitingdots:0.1.0' compile 'pl.tajchert:waitingdots:0.1.0'
compile 'com.soundcloud.android:android-crop:0.9.10@aar' compile 'com.theartofdev.edmodo:android-image-cropper:2.7.0'
compile 'com.melnykov:floatingactionbutton:1.3.0' compile 'com.melnykov:floatingactionbutton:1.3.0'
compile 'com.google.zxing:android-integration:3.1.0' compile 'com.google.zxing:android-integration:3.1.0'
compile 'com.squareup.dagger:dagger:1.2.2' compile 'com.squareup.dagger:dagger:1.2.2'
@ -172,6 +172,7 @@ dependencyVerification {
'com.android.support:preference-v7:75eabe936d1fc3b178450a554c4d433466036f2be6d6dccdf971eac9590fdbf5', 'com.android.support:preference-v7:75eabe936d1fc3b178450a554c4d433466036f2be6d6dccdf971eac9590fdbf5',
'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54', 'com.pnikosis:materialish-progress:d71d80e00717a096784482aee21001a9d299fec3833e4ebd87739ed36cf77c54',
'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c', 'pl.tajchert:waitingdots:2835d49e0787dbcb606c5a60021ced66578503b1e9fddcd7a5ef0cd5f095ba2c',
'com.theartofdev.edmodo:android-image-cropper:72a1b03c5642fe8489061c732e43b10558c850129b576970e4f77a5d4c25317a',
'mobi.upod:time-duration-picker:db469ce0f48dd96b892eac424ed76870e54bf00fe0a28cdcddfbe5f2a226a0e1', 'mobi.upod:time-duration-picker:db469ce0f48dd96b892eac424ed76870e54bf00fe0a28cdcddfbe5f2a226a0e1',
'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794', 'com.codewaves.stickyheadergrid:stickyheadergrid:5b4aa6a52a957cfd55f60f4220c11c0c371385a3cb9786cae03c260dcdef5794',
'com.android.support:appcompat-v7:a3a8e5230359746ed91801579b5fbe4668e3b1c4e6a14c7d67c8f58cb0311752', 'com.android.support:appcompat-v7:a3a8e5230359746ed91801579b5fbe4668e3b1c4e6a14c7d67c8f58cb0311752',
@ -202,7 +203,6 @@ dependencyVerification {
'com.github.bumptech.glide:glide:997de7ac95be6c944d3b8cbe13de11307736ea45451c1b09a6cec7c328ead59f', 'com.github.bumptech.glide:glide:997de7ac95be6c944d3b8cbe13de11307736ea45451c1b09a6cec7c328ead59f',
'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1', 'com.makeramen:roundedimageview:1f5a1865796b308c6cdd114acc6e78408b110f0a62fc63553278fbeacd489cd1',
'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c', 'org.greenrobot:eventbus:180d4212467df06f2fbc9c8d8a2984533ac79c87769ad883bc421612f0b4e17c',
'com.soundcloud.android:android-crop:ffd4b973cf6e97f7d64118a0dc088df50e9066fd5634fe6911dd0c0c5d346177',
'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4', 'com.google.zxing:android-integration:89e56aadf1164bd71e57949163c53abf90af368b51669c0d4a47a163335f95c4',
'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883', 'com.squareup.dagger:dagger:789aca24537022e49f91fc6444078d9de8f1dd99e1bfb090f18491b186967883',
'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb', 'com.amulyakhare:com.amulyakhare.textdrawable:54c92b5fba38cfd316a07e5a30528068f45ce8515a6890f1297df4c401af5dcb',

View File

@ -65,4 +65,6 @@
<color name="logsubmit_confirmation_background">#44ff2d00</color> <color name="logsubmit_confirmation_background">#44ff2d00</color>
<color name="avatar_background">@color/transparent_black_90</color>
</resources> </resources>

View File

@ -381,6 +381,10 @@
<!-- GroupMembersDialog --> <!-- GroupMembersDialog -->
<string name="GroupMembersDialog_me">Me</string> <string name="GroupMembersDialog_me">Me</string>
<!-- CropImageActivity -->
<string name="CropImageActivity_group_avatar">@string/GroupCreateActivity_avatar_content_description</string>
<string name="CropImageActivity_profile_avatar">Avatar</string>
<!-- InputPanel --> <!-- InputPanel -->
<string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Tap and hold to record a voice message, release to send</string> <string name="InputPanel_tap_and_hold_to_record_a_voice_message_release_to_send">Tap and hold to record a voice message, release to send</string>

View File

@ -12,28 +12,25 @@ import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi; import android.support.annotation.RequiresApi;
import android.text.Editable; import android.text.Editable;
import android.text.TextUtils; import android.text.TextUtils;
import android.text.TextWatcher; import android.text.TextWatcher;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.components.LabeledEditText; import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.View; import android.view.View;
import android.view.ViewAnimationUtils; import android.view.ViewAnimationUtils;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton; import com.dd.CircularProgressButton;
import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.InputAwareLayout; import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.emoji.EmojiDrawer; import org.thoughtcrime.securesms.components.emoji.EmojiDrawer;
@ -53,8 +50,6 @@ import org.thoughtcrime.securesms.util.BitmapUtil;
import org.thoughtcrime.securesms.util.DynamicLanguage; import org.thoughtcrime.securesms.util.DynamicLanguage;
import org.thoughtcrime.securesms.util.DynamicRegistrationTheme; import org.thoughtcrime.securesms.util.DynamicRegistrationTheme;
import org.thoughtcrime.securesms.util.DynamicTheme; import org.thoughtcrime.securesms.util.DynamicTheme;
import org.thoughtcrime.securesms.util.FileProviderUtil;
import org.thoughtcrime.securesms.util.IntentUtils;
import org.thoughtcrime.securesms.util.TextSecurePreferences; import org.thoughtcrime.securesms.util.TextSecurePreferences;
import org.thoughtcrime.securesms.util.Util; import org.thoughtcrime.securesms.util.Util;
import org.thoughtcrime.securesms.util.ViewUtil; import org.thoughtcrime.securesms.util.ViewUtil;
@ -67,14 +62,10 @@ import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.security.SecureRandom; import java.security.SecureRandom;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import javax.inject.Inject; import javax.inject.Inject;
import static android.provider.MediaStore.EXTRA_OUTPUT;
@SuppressLint("StaticFieldLeak") @SuppressLint("StaticFieldLeak")
public class CreateProfileActivity extends BaseActionBarActivity implements InjectableType { public class CreateProfileActivity extends BaseActionBarActivity implements InjectableType {
@ -83,8 +74,6 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
public static final String NEXT_INTENT = "next_intent"; public static final String NEXT_INTENT = "next_intent";
public static final String EXCLUDE_SYSTEM = "exclude_system"; public static final String EXCLUDE_SYSTEM = "exclude_system";
private static final int REQUEST_CODE_AVATAR = 1;
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme(); private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage(); private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@ -153,7 +142,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
super.onActivityResult(requestCode, resultCode, data); super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) { switch (requestCode) {
case REQUEST_CODE_AVATAR: case AvatarSelection.REQUEST_CODE_AVATAR:
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped")); Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped"));
Uri inputFile = (data != null ? data.getData() : null); Uri inputFile = (data != null ? data.getData() : null);
@ -166,18 +155,18 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
avatarBytes = null; avatarBytes = null;
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400))); avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400)));
} else { } else {
new Crop(inputFile).output(outputFile).asSquare().start(this); AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar);
} }
} }
break; break;
case Crop.REQUEST_CROP: case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
if (resultCode == Activity.RESULT_OK) { if (resultCode == Activity.RESULT_OK) {
new AsyncTask<Void, Void, byte[]>() { new AsyncTask<Void, Void, byte[]>() {
@Override @Override
protected byte[] doInBackground(Void... params) { protected byte[] doInBackground(Void... params) {
try { try {
BitmapUtil.ScaleResult result = BitmapUtil.createScaledBytes(CreateProfileActivity.this, Crop.getOutput(data), new ProfileMediaConstraints()); BitmapUtil.ScaleResult result = BitmapUtil.createScaledBytes(CreateProfileActivity.this, AvatarSelection.getResultUri(data), new ProfileMediaConstraints());
return result.getBitmap(); return result.getBitmap();
} catch (BitmapDecodingException e) { } catch (BitmapDecodingException e) {
Log.w(TAG, e); Log.w(TAG, e);
@ -220,7 +209,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
this.avatar.setOnClickListener(view -> Permissions.with(this) this.avatar.setOnClickListener(view -> Permissions.with(this)
.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) .request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary() .ifNecessary()
.onAnyResult(this::handleAvatarSelectionWithPermissions) .onAnyResult(this::startAvatarSelection)
.execute()); .execute());
this.name.getInput().addTextChangedListener(new TextWatcher() { this.name.getInput().addTextChangedListener(new TextWatcher() {
@ -354,53 +343,8 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
this.name.setOnClickListener(v -> container.showSoftkey(name.getInput())); this.name.setOnClickListener(v -> container.showSoftkey(name.getInput()));
} }
private Intent createAvatarSelectionIntent(@Nullable File captureFile, boolean includeClear, boolean includeCamera) { private void startAvatarSelection() {
List<Intent> extraIntents = new LinkedList<>(); captureFile = AvatarSelection.startAvatarSelection(this, avatarBytes != null, true);
Intent galleryIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI);
galleryIntent.setType("image/*");
if (!IntentUtils.isResolvable(CreateProfileActivity.this, galleryIntent)) {
galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
}
if (includeCamera) {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (captureFile != null && cameraIntent.resolveActivity(getPackageManager()) != null) {
cameraIntent.putExtra(EXTRA_OUTPUT, FileProviderUtil.getUriFor(this, captureFile));
extraIntents.add(cameraIntent);
}
}
if (includeClear) {
extraIntents.add(new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"));
}
Intent chooserIntent = Intent.createChooser(galleryIntent, getString(R.string.CreateProfileActivity_profile_photo));
if (!extraIntents.isEmpty()) {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0]));
}
return chooserIntent;
}
private void handleAvatarSelectionWithPermissions() {
boolean hasCameraPermission = Permissions.hasAll(this, Manifest.permission.CAMERA);
if (hasCameraPermission) {
try {
captureFile = File.createTempFile("capture", "jpg", getExternalCacheDir());
} catch (IOException e) {
Log.w(TAG, e);
captureFile = null;
}
}
Intent chooserIntent = createAvatarSelectionIntent(captureFile, avatarBytes != null, hasCameraPermission);
startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR);
} }
private void handleUpload() { private void handleUpload() {

View File

@ -27,6 +27,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.text.TextUtils; import android.text.TextUtils;
import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.conversation.ConversationActivity; import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.logging.Log;
import android.view.Menu; import android.view.Menu;
@ -42,7 +43,6 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy; import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget; import com.bumptech.glide.request.target.SimpleTarget;
import com.bumptech.glide.request.transition.Transition; import com.bumptech.glide.request.transition.Transition;
import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.PushRecipientsPanel; import org.thoughtcrime.securesms.components.PushRecipientsPanel;
import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener; import org.thoughtcrime.securesms.components.PushRecipientsPanel.RecipientsPanelChangedListener;
@ -190,7 +190,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
recipientsPanel.setPanelChangeListener(this); recipientsPanel.setPanelChangeListener(this);
findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener()); findViewById(R.id.contacts_button).setOnClickListener(new AddRecipientButtonListener());
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this))); avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_group_white_24dp).asDrawable(this, ContactColors.UNKNOWN_COLOR.toConversationColor(this)));
avatar.setOnClickListener(view -> Crop.pickImage(GroupCreateActivity.this)); avatar.setOnClickListener(view -> AvatarSelection.startAvatarSelection(this, false, false));
} }
private void initializeExistingGroup() { private void initializeExistingGroup() {
@ -293,13 +293,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
} }
break; break;
case Crop.REQUEST_PICK: case AvatarSelection.REQUEST_CODE_AVATAR:
new Crop(data.getData()).output(outputFile).asSquare().start(this); AvatarSelection.circularCropImage(this, data.getData(), outputFile, R.string.CropImageActivity_group_avatar);
break; break;
case Crop.REQUEST_CROP: case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
final Uri resultUri = AvatarSelection.getResultUri(data);
GlideApp.with(this) GlideApp.with(this)
.asBitmap() .asBitmap()
.load(Crop.getOutput(data)) .load(resultUri)
.skipMemoryCache(true) .skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop() .centerCrop()
@ -307,7 +308,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
.into(new SimpleTarget<Bitmap>() { .into(new SimpleTarget<Bitmap>() {
@Override @Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) { public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
setAvatar(Crop.getOutput(data), resource); setAvatar(resultUri, resource);
} }
}); });
} }

View File

@ -0,0 +1,115 @@
package org.thoughtcrime.securesms.avatar;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.provider.MediaStore;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.v4.content.ContextCompat;
import com.theartofdev.edmodo.cropper.CropImage;
import com.theartofdev.edmodo.cropper.CropImageView;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.logging.Log;
import org.thoughtcrime.securesms.permissions.Permissions;
import org.thoughtcrime.securesms.util.FileProviderUtil;
import org.thoughtcrime.securesms.util.IntentUtils;
import java.io.File;
import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import static android.provider.MediaStore.EXTRA_OUTPUT;
public final class AvatarSelection {
private static final String TAG = AvatarSelection.class.getSimpleName();
private AvatarSelection() {
}
public static final int REQUEST_CODE_CROP_IMAGE = CropImage.CROP_IMAGE_ACTIVITY_REQUEST_CODE;
public static final int REQUEST_CODE_AVATAR = REQUEST_CODE_CROP_IMAGE + 1;
/**
* Returns result on {@link #REQUEST_CODE_CROP_IMAGE}
*/
public static void circularCropImage(Activity activity, Uri inputFile, Uri outputFile, @StringRes int title) {
CropImage.activity(inputFile)
.setGuidelines(CropImageView.Guidelines.ON)
.setAspectRatio(1, 1)
.setCropShape(CropImageView.CropShape.OVAL)
.setOutputUri(outputFile)
.setAllowRotation(true)
.setAllowFlipping(true)
.setBackgroundColor(ContextCompat.getColor(activity, R.color.avatar_background))
.setActivityTitle(activity.getString(title))
.start(activity);
}
public static Uri getResultUri(Intent data) {
return CropImage.getActivityResult(data).getUri();
}
/**
* Returns result on {@link #REQUEST_CODE_AVATAR}
*
* @return Temporary capture file if created.
*/
public static File startAvatarSelection(Activity activity, boolean includeClear, boolean attemptToIncludeCamera) {
File captureFile = null;
if (attemptToIncludeCamera) {
if (Permissions.hasAll(activity, Manifest.permission.CAMERA)) {
try {
captureFile = File.createTempFile("capture", "jpg", activity.getExternalCacheDir());
} catch (IOException e) {
Log.w(TAG, e);
captureFile = null;
}
}
}
Intent chooserIntent = createAvatarSelectionIntent(activity, captureFile, includeClear);
activity.startActivityForResult(chooserIntent, REQUEST_CODE_AVATAR);
return captureFile;
}
private static Intent createAvatarSelectionIntent(Context context, @Nullable File tempCaptureFile, boolean includeClear) {
List<Intent> extraIntents = new LinkedList<>();
Intent galleryIntent = new Intent(Intent.ACTION_PICK);
galleryIntent.setDataAndType(android.provider.MediaStore.Images.Media.INTERNAL_CONTENT_URI, "image/*");
if (!IntentUtils.isResolvable(context, galleryIntent)) {
galleryIntent = new Intent(Intent.ACTION_GET_CONTENT);
galleryIntent.setType("image/*");
}
if (tempCaptureFile != null) {
Intent cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
if (cameraIntent.resolveActivity(context.getPackageManager()) != null) {
cameraIntent.putExtra(EXTRA_OUTPUT, FileProviderUtil.getUriFor(context, tempCaptureFile));
extraIntents.add(cameraIntent);
}
}
if (includeClear) {
extraIntents.add(new Intent("org.thoughtcrime.securesms.action.CLEAR_PROFILE_PHOTO"));
}
Intent chooserIntent = Intent.createChooser(galleryIntent, context.getString(R.string.CreateProfileActivity_profile_photo));
if (!extraIntents.isEmpty()) {
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, extraIntents.toArray(new Intent[0]));
}
return chooserIntent;
}
}