diff --git a/AndroidManifest.xml b/AndroidManifest.xml
index 7e5a07bf27..251d711e54 100644
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -403,7 +403,8 @@
android:theme="@style/TextSecure.ScribbleTheme"
android:configChanges="touchscreen|keyboard|keyboardHidden|orientation|screenLayout|screenSize"/>
-
+
#44ff2d00
+ @color/transparent_black_90
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 818bb65838..6128316348 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -381,6 +381,10 @@
Me
+
+ @string/GroupCreateActivity_avatar_content_description
+ Avatar
+
Tap and hold to record a voice message, release to send
diff --git a/src/org/thoughtcrime/securesms/CreateProfileActivity.java b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
index aad59d6642..7053451a42 100644
--- a/src/org/thoughtcrime/securesms/CreateProfileActivity.java
+++ b/src/org/thoughtcrime/securesms/CreateProfileActivity.java
@@ -12,28 +12,25 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
-import android.provider.MediaStore;
import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
import android.support.annotation.RequiresApi;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
+import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.components.LabeledEditText;
import org.thoughtcrime.securesms.logging.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.WindowManager;
-import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.dd.CircularProgressButton;
-import com.soundcloud.android.crop.Crop;
import org.thoughtcrime.securesms.components.InputAwareLayout;
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.DynamicRegistrationTheme;
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.Util;
import org.thoughtcrime.securesms.util.ViewUtil;
@@ -67,14 +62,10 @@ import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.security.SecureRandom;
-import java.util.LinkedList;
-import java.util.List;
import java.util.concurrent.ExecutionException;
import javax.inject.Inject;
-import static android.provider.MediaStore.EXTRA_OUTPUT;
-
@SuppressLint("StaticFieldLeak")
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 EXCLUDE_SYSTEM = "exclude_system";
- private static final int REQUEST_CODE_AVATAR = 1;
-
private final DynamicTheme dynamicTheme = new DynamicRegistrationTheme();
private final DynamicLanguage dynamicLanguage = new DynamicLanguage();
@@ -153,7 +142,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
- case REQUEST_CODE_AVATAR:
+ case AvatarSelection.REQUEST_CODE_AVATAR:
if (resultCode == Activity.RESULT_OK) {
Uri outputFile = Uri.fromFile(new File(getCacheDir(), "cropped"));
Uri inputFile = (data != null ? data.getData() : null);
@@ -166,18 +155,18 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
avatarBytes = null;
avatar.setImageDrawable(new ResourceContactPhoto(R.drawable.ic_camera_alt_white_24dp).asDrawable(this, getResources().getColor(R.color.grey_400)));
} else {
- new Crop(inputFile).output(outputFile).asSquare().start(this);
+ AvatarSelection.circularCropImage(this, inputFile, outputFile, R.string.CropImageActivity_profile_avatar);
}
}
break;
- case Crop.REQUEST_CROP:
+ case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
if (resultCode == Activity.RESULT_OK) {
new AsyncTask() {
@Override
protected byte[] doInBackground(Void... params) {
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();
} catch (BitmapDecodingException e) {
Log.w(TAG, e);
@@ -220,7 +209,7 @@ public class CreateProfileActivity extends BaseActionBarActivity implements Inje
this.avatar.setOnClickListener(view -> Permissions.with(this)
.request(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE)
.ifNecessary()
- .onAnyResult(this::handleAvatarSelectionWithPermissions)
+ .onAnyResult(this::startAvatarSelection)
.execute());
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()));
}
- private Intent createAvatarSelectionIntent(@Nullable File captureFile, boolean includeClear, boolean includeCamera) {
- List extraIntents = new LinkedList<>();
- 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 startAvatarSelection() {
+ captureFile = AvatarSelection.startAvatarSelection(this, avatarBytes != null, true);
}
private void handleUpload() {
diff --git a/src/org/thoughtcrime/securesms/GroupCreateActivity.java b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
index 0eccae3149..44cd8aa280 100644
--- a/src/org/thoughtcrime/securesms/GroupCreateActivity.java
+++ b/src/org/thoughtcrime/securesms/GroupCreateActivity.java
@@ -27,6 +27,7 @@ import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.text.TextUtils;
+import org.thoughtcrime.securesms.avatar.AvatarSelection;
import org.thoughtcrime.securesms.conversation.ConversationActivity;
import org.thoughtcrime.securesms.logging.Log;
import android.view.Menu;
@@ -42,7 +43,6 @@ import android.widget.Toast;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.request.target.SimpleTarget;
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.RecipientsPanelChangedListener;
@@ -190,7 +190,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
recipientsPanel.setPanelChangeListener(this);
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.setOnClickListener(view -> Crop.pickImage(GroupCreateActivity.this));
+ avatar.setOnClickListener(view -> AvatarSelection.startAvatarSelection(this, false, false));
}
private void initializeExistingGroup() {
@@ -293,13 +293,14 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
}
break;
- case Crop.REQUEST_PICK:
- new Crop(data.getData()).output(outputFile).asSquare().start(this);
+ case AvatarSelection.REQUEST_CODE_AVATAR:
+ AvatarSelection.circularCropImage(this, data.getData(), outputFile, R.string.CropImageActivity_group_avatar);
break;
- case Crop.REQUEST_CROP:
+ case AvatarSelection.REQUEST_CODE_CROP_IMAGE:
+ final Uri resultUri = AvatarSelection.getResultUri(data);
GlideApp.with(this)
.asBitmap()
- .load(Crop.getOutput(data))
+ .load(resultUri)
.skipMemoryCache(true)
.diskCacheStrategy(DiskCacheStrategy.NONE)
.centerCrop()
@@ -307,7 +308,7 @@ public class GroupCreateActivity extends PassphraseRequiredActionBarActivity
.into(new SimpleTarget() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition super Bitmap> transition) {
- setAvatar(Crop.getOutput(data), resource);
+ setAvatar(resultUri, resource);
}
});
}
diff --git a/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java
new file mode 100644
index 0000000000..a0bb533d9e
--- /dev/null
+++ b/src/org/thoughtcrime/securesms/avatar/AvatarSelection.java
@@ -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 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;
+ }
+}