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 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; + } +}