Nicer looking attachment type selector

Closes #4367
// FREEBIE
This commit is contained in:
Moxie Marlinspike
2015-10-28 09:47:09 -07:00
parent 2941ac0e2c
commit be0ca330f5
33 changed files with 487 additions and 16 deletions

View File

@@ -62,6 +62,7 @@ import org.thoughtcrime.securesms.TransportOptions.OnTransportChangedListener;
import org.thoughtcrime.securesms.audio.AudioSlidePlayer;
import org.thoughtcrime.securesms.color.MaterialColor;
import org.thoughtcrime.securesms.components.AnimatingToggle;
import org.thoughtcrime.securesms.components.AttachmentTypeSelector;
import org.thoughtcrime.securesms.components.ComposeText;
import org.thoughtcrime.securesms.components.InputAwareLayout;
import org.thoughtcrime.securesms.components.KeyboardAwareLinearLayout.OnKeyboardShownListener;
@@ -181,14 +182,14 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private View composeBubble;
private ReminderView reminderView;
private AttachmentTypeSelectorAdapter attachmentAdapter;
private AttachmentManager attachmentManager;
private BroadcastReceiver securityUpdateReceiver;
private BroadcastReceiver groupUpdateReceiver;
private EmojiDrawer emojiDrawer;
private EmojiToggle emojiToggle;
protected HidingImageButton quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer;
private AttachmentTypeSelector attachmentTypeSelector;
private AttachmentManager attachmentManager;
private BroadcastReceiver securityUpdateReceiver;
private BroadcastReceiver groupUpdateReceiver;
private EmojiDrawer emojiDrawer;
private EmojiToggle emojiToggle;
protected HidingImageButton quickAttachmentToggle;
private QuickAttachmentDrawer quickAttachmentDrawer;
private Recipients recipients;
private long threadId;
@@ -673,8 +674,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
private void handleAddAttachment() {
if (this.isMmsEnabled || isSecureText) {
new AlertDialogWrapper.Builder(this).setAdapter(attachmentAdapter, new AttachmentTypeListener())
.show();
attachmentTypeSelector.show(this, attachButton);
} else {
handleManualMmsRequired();
}
@@ -865,8 +865,8 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
composeBubble.getBackground().setColorFilter(defaultColor, PorterDuff.Mode.MULTIPLY);
colors.recycle();
attachmentAdapter = new AttachmentTypeSelectorAdapter(this);
attachmentManager = new AttachmentManager(this, this);
attachmentTypeSelector = new AttachmentTypeSelector(this, new AttachmentTypeListener());
attachmentManager = new AttachmentManager(this, this);
SendButtonListener sendButtonListener = new SendButtonListener();
ComposeKeyPressedListener composeKeyPressedListener = new ComposeKeyPressedListener();
@@ -1348,11 +1348,10 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
// Listeners
private class AttachmentTypeListener implements DialogInterface.OnClickListener {
private class AttachmentTypeListener implements AttachmentTypeSelector.AttachmentClickedListener {
@Override
public void onClick(DialogInterface dialog, int which) {
addAttachment(attachmentAdapter.buttonToCommand(which));
dialog.dismiss();
public void onClick(int type) {
addAttachment(type);
}
}

View File

@@ -0,0 +1,243 @@
package org.thoughtcrime.securesms.components;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.Activity;
import android.content.Context;
import android.graphics.drawable.BitmapDrawable;
import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewAnimationUtils;
import android.view.ViewTreeObserver;
import android.view.animation.Animation;
import android.view.animation.AnimationSet;
import android.view.animation.OvershootInterpolator;
import android.view.animation.ScaleAnimation;
import android.view.animation.TranslateAnimation;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.PopupWindow;
import org.thoughtcrime.securesms.R;
import org.thoughtcrime.securesms.util.ViewUtil;
public class AttachmentTypeSelector extends PopupWindow {
public static final int ADD_IMAGE = 1;
public static final int ADD_VIDEO = 2;
public static final int ADD_SOUND = 3;
public static final int ADD_CONTACT_INFO = 4;
public static final int TAKE_PHOTO = 5;
private static final int ANIMATION_DURATION = 300;
private static final String TAG = AttachmentTypeSelector.class.getSimpleName();
private final @NonNull ImageView imageButton;
private final @NonNull ImageView audioButton;
private final @NonNull ImageView videoButton;
private final @NonNull ImageView contactButton;
private final @NonNull ImageView cameraButton;
private @Nullable View currentAnchor;
private @Nullable AttachmentClickedListener listener;
public AttachmentTypeSelector(@NonNull Context context, @Nullable AttachmentClickedListener listener) {
super(context);
LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
LinearLayout layout = (LinearLayout) inflater.inflate(R.layout.attachment_type_selector, null, true);
this.listener = listener;
this.imageButton = ViewUtil.findById(layout, R.id.gallery_button);
this.audioButton = ViewUtil.findById(layout, R.id.audio_button);
this.videoButton = ViewUtil.findById(layout, R.id.video_button);
this.contactButton = ViewUtil.findById(layout, R.id.contact_button);
this.cameraButton = ViewUtil.findById(layout, R.id.camera_button);
this.imageButton.setOnClickListener(new PropagatingClickListener(ADD_IMAGE));
this.audioButton.setOnClickListener(new PropagatingClickListener(ADD_SOUND));
this.videoButton.setOnClickListener(new PropagatingClickListener(ADD_VIDEO));
this.contactButton.setOnClickListener(new PropagatingClickListener(ADD_CONTACT_INFO));
this.cameraButton.setOnClickListener(new PropagatingClickListener(TAKE_PHOTO));
setContentView(layout);
setWidth(LinearLayout.LayoutParams.MATCH_PARENT);
setHeight(LinearLayout.LayoutParams.WRAP_CONTENT);
setBackgroundDrawable(new BitmapDrawable());
setAnimationStyle(0);
setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
setFocusable(true);
setTouchable(true);
}
public void show(@NonNull Activity activity, final @NonNull View anchor) {
this.currentAnchor = anchor;
int screenHeight = activity.getWindowManager().getDefaultDisplay().getHeight();
showAtLocation(anchor, Gravity.NO_GRAVITY, 0, screenHeight - getHeight());
getContentView().getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
getContentView().getViewTreeObserver().removeGlobalOnLayoutListener(this);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowInCircular(anchor, getContentView());
} else {
animateWindowInTranslate(getContentView());
}
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateButtonIn(imageButton, ANIMATION_DURATION / 2);
animateButtonIn(cameraButton, ANIMATION_DURATION / 2);
animateButtonIn(audioButton, ANIMATION_DURATION / 3);
animateButtonIn(videoButton, ANIMATION_DURATION / 4);
animateButtonIn(contactButton, 0);
}
}
@Override
public void dismiss() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
animateWindowOutCircular(currentAnchor, getContentView());
} else {
animateWindowOutTranslate(getContentView());
}
}
public void setListener(@Nullable AttachmentClickedListener listener) {
this.listener = listener;
}
private void animateButtonIn(View button, int delay) {
AnimationSet animation = new AnimationSet(true);
Animation scale = new ScaleAnimation(0.0f, 1.0f, 0.0f, 1.0f,
Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.0f);
animation.addAnimation(scale);
animation.setInterpolator(new OvershootInterpolator(1));
animation.setDuration(ANIMATION_DURATION);
animation.setStartOffset(delay);
button.startAnimation(animation);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateWindowInCircular(@Nullable View anchor, @NonNull View contentView) {
Pair<Integer, Integer> coordinates = getClickOrigin(anchor, contentView);
Animator animator = ViewAnimationUtils.createCircularReveal(contentView,
coordinates.first,
coordinates.second,
0,
Math.max(contentView.getWidth(), contentView.getHeight()));
animator.setDuration(ANIMATION_DURATION);
animator.start();
}
private void animateWindowInTranslate(@NonNull View contentView) {
Animation animation = new TranslateAnimation(0, 0, contentView.getHeight(), 0);
animation.setDuration(ANIMATION_DURATION);
getContentView().startAnimation(animation);
}
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
private void animateWindowOutCircular(@Nullable View anchor, @NonNull View contentView) {
Pair<Integer, Integer> coordinates = getClickOrigin(anchor, contentView);
Animator animator = ViewAnimationUtils.createCircularReveal(getContentView(),
coordinates.first,
coordinates.second,
Math.max(getContentView().getWidth(), getContentView().getHeight()),
0);
animator.setDuration(ANIMATION_DURATION);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
AttachmentTypeSelector.super.dismiss();
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
private void animateWindowOutTranslate(@NonNull View contentView) {
Animation animation = new TranslateAnimation(0, 0, 0, contentView.getTop() + contentView.getHeight());
animation.setDuration(ANIMATION_DURATION);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
AttachmentTypeSelector.super.dismiss();
}
@Override
public void onAnimationRepeat(Animation animation) {
}
});
getContentView().startAnimation(animation);
}
private Pair<Integer, Integer> getClickOrigin(@Nullable View anchor, @NonNull View contentView) {
if (anchor == null) return new Pair<>(0, 0);
final int[] anchorCoordinates = new int[2];
anchor.getLocationOnScreen(anchorCoordinates);
anchorCoordinates[0] += anchor.getWidth() / 2;
anchorCoordinates[1] += anchor.getHeight() / 2;
final int[] contentCoordinates = new int[2];
contentView.getLocationOnScreen(contentCoordinates);
int x = anchorCoordinates[0] - contentCoordinates[0];
int y = anchorCoordinates[1] - contentCoordinates[1];
return new Pair<>(x, y);
}
private class PropagatingClickListener implements View.OnClickListener {
private final int type;
private PropagatingClickListener(int type) {
this.type = type;
}
@Override
public void onClick(View v) {
animateWindowOutTranslate(getContentView());
if (listener != null) listener.onClick(type);
}
}
public interface AttachmentClickedListener {
public void onClick(int type);
}
}

View File

@@ -0,0 +1,40 @@
package org.thoughtcrime.securesms.components;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
import android.widget.ImageView;
import org.thoughtcrime.securesms.R;
public class CircleColorImageView extends ImageView {
public CircleColorImageView(Context context) {
this(context, null);
}
public CircleColorImageView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public CircleColorImageView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
int circleColor = Color.WHITE;
if (attrs != null) {
TypedArray typedArray = context.getTheme().obtainStyledAttributes(attrs, R.styleable.CircleColorImageView, 0, 0);
circleColor = typedArray.getColor(R.styleable.CircleColorImageView_circleColor, Color.WHITE);
typedArray.recycle();
}
Drawable circle = context.getResources().getDrawable(R.drawable.circle_tintable);
circle.setColorFilter(circleColor, PorterDuff.Mode.SRC_IN);
setBackgroundDrawable(circle);
}
}