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.net.Uri; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.app.LoaderManager; 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_GALLERY = 1; public static final int ADD_DOCUMENT = 2; public static final int ADD_SOUND = 3; public static final int ADD_CONTACT_INFO = 4; public static final int TAKE_PHOTO = 5; public static final int ADD_LOCATION = 6; public static final int ADD_GIF = 7; 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 documentButton; private final @NonNull ImageView contactButton; private final @NonNull ImageView cameraButton; private final @NonNull ImageView locationButton; private final @NonNull ImageView gifButton; private final @NonNull ImageView closeButton; private @Nullable View currentAnchor; private @Nullable AttachmentClickedListener listener; public AttachmentTypeSelector(@NonNull Context context, @NonNull LoaderManager loaderManager, @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); RecentPhotoViewRail recentPhotos = ViewUtil.findById(layout, R.id.recent_photos); this.listener = listener; this.imageButton = ViewUtil.findById(layout, R.id.gallery_button); this.audioButton = ViewUtil.findById(layout, R.id.audio_button); this.documentButton = ViewUtil.findById(layout, R.id.document_button); this.contactButton = ViewUtil.findById(layout, R.id.contact_button); this.cameraButton = ViewUtil.findById(layout, R.id.camera_button); this.locationButton = ViewUtil.findById(layout, R.id.location_button); this.gifButton = ViewUtil.findById(layout, R.id.giphy_button); this.closeButton = ViewUtil.findById(layout, R.id.close_button); this.imageButton.setOnClickListener(new PropagatingClickListener(ADD_GALLERY)); this.audioButton.setOnClickListener(new PropagatingClickListener(ADD_SOUND)); this.documentButton.setOnClickListener(new PropagatingClickListener(ADD_DOCUMENT)); this.contactButton.setOnClickListener(new PropagatingClickListener(ADD_CONTACT_INFO)); this.cameraButton.setOnClickListener(new PropagatingClickListener(TAKE_PHOTO)); this.locationButton.setOnClickListener(new PropagatingClickListener(ADD_LOCATION)); this.gifButton.setOnClickListener(new PropagatingClickListener(ADD_GIF)); this.closeButton.setOnClickListener(new CloseClickListener()); recentPhotos.setListener(new RecentPhotoSelectedListener()); if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { ViewUtil.findById(layout, R.id.location_linear_layout).setVisibility(View.INVISIBLE); } 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); loaderManager.initLoader(1, null, recentPhotos); } public void show(@NonNull Activity activity, final @NonNull View anchor) { this.currentAnchor = anchor; showAtLocation(anchor, Gravity.BOTTOM, 0, 0); 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(locationButton, ANIMATION_DURATION / 3); animateButtonIn(documentButton, ANIMATION_DURATION / 4); animateButtonIn(gifButton, ANIMATION_DURATION / 4); animateButtonIn(contactButton, 0); animateButtonIn(closeButton, 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 RecentPhotoSelectedListener implements RecentPhotoViewRail.OnItemClickedListener { @Override public void onItemClicked(Uri uri) { animateWindowOutTranslate(getContentView()); if (listener != null) listener.onQuickAttachment(uri); } } 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); } } private class CloseClickListener implements View.OnClickListener { @Override public void onClick(View v) { dismiss(); } } public interface AttachmentClickedListener { public void onClick(int type); public void onQuickAttachment(Uri uri); } }