diff --git a/res/drawable-hdpi/transfer_controls_background.9.png b/res/drawable-hdpi/transfer_controls_background.9.png new file mode 100644 index 0000000000..0e503fb512 Binary files /dev/null and b/res/drawable-hdpi/transfer_controls_background.9.png differ diff --git a/res/drawable-mdpi/transfer_controls_background.9.png b/res/drawable-mdpi/transfer_controls_background.9.png new file mode 100644 index 0000000000..3e63204968 Binary files /dev/null and b/res/drawable-mdpi/transfer_controls_background.9.png differ diff --git a/res/drawable-xhdpi/transfer_controls_background.9.png b/res/drawable-xhdpi/transfer_controls_background.9.png new file mode 100644 index 0000000000..8f4d75a97b Binary files /dev/null and b/res/drawable-xhdpi/transfer_controls_background.9.png differ diff --git a/res/drawable-xxhdpi/transfer_controls_background.9.png b/res/drawable-xxhdpi/transfer_controls_background.9.png new file mode 100644 index 0000000000..cdbc966d88 Binary files /dev/null and b/res/drawable-xxhdpi/transfer_controls_background.9.png differ diff --git a/res/drawable-xxxhdpi/transfer_controls_background.9.png b/res/drawable-xxxhdpi/transfer_controls_background.9.png new file mode 100644 index 0000000000..4aad138cfd Binary files /dev/null and b/res/drawable-xxxhdpi/transfer_controls_background.9.png differ diff --git a/res/layout/transfer_controls_view.xml b/res/layout/transfer_controls_view.xml index 2a8888929f..f696f9d681 100644 --- a/res/layout/transfer_controls_view.xml +++ b/res/layout/transfer_controls_view.xml @@ -4,19 +4,22 @@ - + \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml index a4666889bf..481601a441 100644 --- a/res/values/dimens.xml +++ b/res/values/dimens.xml @@ -34,4 +34,7 @@ 250dp 52dp + + 150dp + 70dp diff --git a/res/values/strings.xml b/res/values/strings.xml index c321f70f97..dac80e337b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -384,6 +384,11 @@ Registration error TextSecure registration has encountered a problem. + + Image + Audio + Video + Received corrupted key exchange message! diff --git a/src/org/thoughtcrime/securesms/components/AnimatingToggle.java b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java index 03a13f73b8..30b100b89a 100644 --- a/src/org/thoughtcrime/securesms/components/AnimatingToggle.java +++ b/src/org/thoughtcrime/securesms/components/AnimatingToggle.java @@ -3,7 +3,6 @@ package org.thoughtcrime.securesms.components; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; -import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; import android.view.View; import android.view.ViewGroup; @@ -12,21 +11,27 @@ import android.view.animation.AnimationUtils; import android.widget.FrameLayout; import org.thoughtcrime.securesms.R; +import org.thoughtcrime.securesms.util.ViewUtil; public class AnimatingToggle extends FrameLayout { private View current; + private final Animation inAnimation; + private final Animation outAnimation; + public AnimatingToggle(Context context) { - super(context); + this(context, null); } public AnimatingToggle(Context context, AttributeSet attrs) { - super(context, attrs); + this(context, attrs, 0); } public AnimatingToggle(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); + this.outAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.animation_toggle_out); + this.inAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.animation_toggle_in); } @Override @@ -43,45 +48,10 @@ public class AnimatingToggle extends FrameLayout { } public void display(@Nullable View view) { - display(view, true); - } - - protected void display(@Nullable View view, boolean animated) { if (view == current) return; - - if (animated) { - if (current != null) animateOut(current, AnimationUtils.loadAnimation(getContext(), R.anim.animation_toggle_out)); - if (view != null) animateIn(view, AnimationUtils.loadAnimation(getContext(), R.anim.animation_toggle_in)); - } else { - if (current != null) current.setVisibility(GONE); - if (view != null) view.setVisibility(VISIBLE); - } + if (current != null) ViewUtil.animateOut(current, outAnimation); + if (view != null) ViewUtil.animateIn(view, inAnimation); current = view; } - - private void animateOut(final View view, Animation animation) { - animation.setAnimationListener(new Animation.AnimationListener() { - @Override - public void onAnimationStart(Animation animation) { - } - - @Override - public void onAnimationEnd(Animation animation) { - view.setVisibility(View.GONE); - } - - @Override - public void onAnimationRepeat(Animation animation) { - } - }); - - view.startAnimation(animation); - } - - private void animateIn(View view, Animation animation) { - animation.setInterpolator(new FastOutSlowInInterpolator()); - view.setVisibility(View.VISIBLE); - view.startAnimation(animation); - } } diff --git a/src/org/thoughtcrime/securesms/components/ThumbnailView.java b/src/org/thoughtcrime/securesms/components/ThumbnailView.java index 33d5f41d30..c57616f0ce 100644 --- a/src/org/thoughtcrime/securesms/components/ThumbnailView.java +++ b/src/org/thoughtcrime/securesms/components/ThumbnailView.java @@ -91,7 +91,6 @@ public class ThumbnailView extends FrameLayout { private TransferControlView getTransferControls() { if (transferControls == null) transferControls = ViewUtil.inflateStub(this, R.id.transfer_controls_stub); - return transferControls; } @@ -99,9 +98,10 @@ public class ThumbnailView extends FrameLayout { this.backgroundColorHint = color; } - public void setImageResource(@Nullable MasterSecret masterSecret, - long id, long timestamp, - @NonNull ListenableFutureTask slideDeckFuture) + public void setImageResource(@Nullable MasterSecret masterSecret, + long id, + long timestamp, + @NonNull ListenableFutureTask slideDeckFuture) { if (this.slideDeckFuture != null && this.slideDeckListener != null) { this.slideDeckFuture.removeListener(this.slideDeckListener); @@ -126,15 +126,17 @@ public class ThumbnailView extends FrameLayout { Log.w(TAG, "Not re-loading slide " + slide.getPart().getPartId()); return; } + if (!isContextValid()) { Log.w(TAG, "Not loading slide, context is invalid"); return; } - Log.w(TAG, "loading part with id " + slide.getPart().getPartId() + ", progress " + slide.getTransferProgress()); + Log.w(TAG, "loading part with id " + slide.getPart().getPartId() + + ", progress " + slide.getTransferProgress()); this.slide = slide; - buildGlideRequest(slide, masterSecret).into(image); + loadInto(slide, masterSecret, image); if (this.slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_DONE) { setOnClickListener(new ThumbnailClickDispatcher()); @@ -145,7 +147,6 @@ public class ThumbnailView extends FrameLayout { if (!hideControls) { getTransferControls().setSlide(slide); getTransferControls().setDownloadClickListener(new DownloadClickDispatcher()); - getTransferControls().setVisibility(View.VISIBLE); } } @@ -189,29 +190,27 @@ public class ThumbnailView extends FrameLayout { !((Activity)getContext()).isDestroyed(); } - private GenericRequestBuilder buildGlideRequest(@NonNull Slide slide, - @Nullable MasterSecret masterSecret) + private void loadInto(@NonNull Slide slide, + @Nullable MasterSecret masterSecret, + @NonNull ImageView view) { - final GenericRequestBuilder builder; if (slide.getThumbnailUri() != null) { - builder = buildThumbnailGlideRequest(slide, masterSecret); + buildThumbnailGlideRequest(slide, masterSecret).into(view); + } else if (!slide.isInProgress()) { + buildPlaceholderGlideRequest(slide).into(view); } else { - builder = buildPlaceholderGlideRequest(slide); - } - - if (slide.getTransferProgress() != PartDatabase.TRANSFER_PROGRESS_DONE && !hideControls) { - return builder; - } else { - return builder.error(R.drawable.ic_missing_thumbnail_picture); + Glide.clear(view); } } private GenericRequestBuilder buildThumbnailGlideRequest(Slide slide, MasterSecret masterSecret) { - final GenericRequestBuilder builder; + if (slide.isDraft()) builder = buildDraftGlideRequest(slide, masterSecret); else builder = buildPartGlideRequest(slide, masterSecret); - return builder; + + if (slide.isInProgress()) return builder; + else return builder.error(R.drawable.ic_missing_thumbnail_picture); } private GenericRequestBuilder buildDraftGlideRequest(Slide slide, MasterSecret masterSecret) { diff --git a/src/org/thoughtcrime/securesms/components/TransferControlView.java b/src/org/thoughtcrime/securesms/components/TransferControlView.java index c2406e1b00..282a7ae6aa 100644 --- a/src/org/thoughtcrime/securesms/components/TransferControlView.java +++ b/src/org/thoughtcrime/securesms/components/TransferControlView.java @@ -3,9 +3,18 @@ package org.thoughtcrime.securesms.components; import android.content.Context; import android.support.annotation.NonNull; import android.support.annotation.Nullable; +import android.support.v4.view.animation.FastOutSlowInInterpolator; import android.util.AttributeSet; -import android.widget.ImageButton; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.widget.FrameLayout; +import android.widget.TextView; +import com.nineoldandroids.animation.Animator; +import com.nineoldandroids.animation.ValueAnimator; +import com.nineoldandroids.animation.ValueAnimator.AnimatorUpdateListener; import com.pnikosis.materialishprogress.ProgressWheel; import org.thoughtcrime.securesms.R; @@ -17,10 +26,18 @@ import org.thoughtcrime.securesms.util.ViewUtil; import de.greenrobot.event.EventBus; -public class TransferControlView extends AnimatingToggle { - private Slide slide; - private ProgressWheel progressWheel; - private ImageButton downloadButton; +public class TransferControlView extends FrameLayout { + private static final int TRANSITION_MS = 300; + + @Nullable private Slide slide; + @Nullable private View current; + + private final ProgressWheel progressWheel; + private final TextView downloadDetails; + private final Animation inAnimation; + private final Animation outAnimation; + private final int contractedWidth; + private final int expandedWidth; public TransferControlView(Context context) { this(context, null); @@ -33,8 +50,18 @@ public class TransferControlView extends AnimatingToggle { public TransferControlView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); inflate(context, R.layout.transfer_controls_view, this); - this.progressWheel = ViewUtil.findById(this, R.id.progress_wheel); - this.downloadButton = ViewUtil.findById(this, R.id.download_button); + setBackgroundResource(R.drawable.transfer_controls_background); + setVisibility(GONE); + this.progressWheel = ViewUtil.findById(this, R.id.progress_wheel); + this.downloadDetails = ViewUtil.findById(this, R.id.download_details); + this.contractedWidth = getResources().getDimensionPixelSize(R.dimen.transfer_controls_contracted_width); + this.expandedWidth = getResources().getDimensionPixelSize(R.dimen.transfer_controls_expanded_width); + this.outAnimation = new AlphaAnimation(1f, 0f); + this.inAnimation = new AlphaAnimation(0f, 1f); + this.outAnimation.setInterpolator(new FastOutSlowInInterpolator()); + this.inAnimation.setInterpolator(new FastOutSlowInInterpolator()); + this.outAnimation.setDuration(TRANSITION_MS); + this.inAnimation.setDuration(TRANSITION_MS); } @Override protected void onAttachedToWindow() { @@ -49,13 +76,13 @@ public class TransferControlView extends AnimatingToggle { public void setSlide(final @NonNull Slide slide) { this.slide = slide; - if (slide.getTransferProgress() == PartDatabase.TRANSFER_PROGRESS_STARTED) { showProgressSpinner(); } else if (slide.isPendingDownload()) { - display(downloadButton); + downloadDetails.setText(slide.getContentDescription()); + display(downloadDetails); } else { - display(null, false); + display(null); } } @@ -65,12 +92,57 @@ public class TransferControlView extends AnimatingToggle { } public void setDownloadClickListener(final @Nullable OnClickListener listener) { - downloadButton.setOnClickListener(listener); + downloadDetails.setOnClickListener(listener); } public void clear() { - display(null, false); - slide = null; + clearAnimation(); + setVisibility(GONE); + if (current != null) { + current.clearAnimation(); + current.setVisibility(GONE); + } + current = null; + slide = null; + } + + private void display(@Nullable final View view) { + final int sourceWidth = current == downloadDetails ? expandedWidth : contractedWidth; + final int targetWidth = view == downloadDetails ? expandedWidth : contractedWidth; + + if (current == view || current == null) { + ViewGroup.LayoutParams layoutParams = getLayoutParams(); + layoutParams.width = targetWidth; + setLayoutParams(layoutParams); + } else { + ViewUtil.animateOut(current, outAnimation); + Animator anim = getWidthAnimator(sourceWidth, targetWidth); + anim.start(); + } + + if (view == null) { + ViewUtil.animateOut(this, outAnimation); + } else { + ViewUtil.animateIn(this, inAnimation); + ViewUtil.animateIn(view, inAnimation); + } + + current = view; + } + + private Animator getWidthAnimator(final int from, final int to) { + final ValueAnimator anim = ValueAnimator.ofInt(from, to); + anim.addUpdateListener(new AnimatorUpdateListener() { + @Override public void onAnimationUpdate(ValueAnimator animation) { + final int val = (Integer)animation.getAnimatedValue(); + final ViewGroup.LayoutParams layoutParams = getLayoutParams(); + layoutParams.width = val; + setLayoutParams(layoutParams); + } + }); + anim.setInterpolator(new FastOutSlowInInterpolator()); + anim.setDuration(TRANSITION_MS); + return anim; } @SuppressWarnings("unused") @@ -79,7 +151,6 @@ public class TransferControlView extends AnimatingToggle { Util.runOnMain(new Runnable() { @Override public void run() { progressWheel.setInstantProgress(((float)event.progress) / event.total); - if (event.progress >= event.total) display(null); } }); } diff --git a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java index e401918a55..3ede7180eb 100644 --- a/src/org/thoughtcrime/securesms/mms/AttachmentManager.java +++ b/src/org/thoughtcrime/securesms/mms/AttachmentManager.java @@ -33,6 +33,8 @@ import android.view.animation.AlphaAnimation; import android.view.animation.Animation; import android.widget.Toast; +import junit.framework.Assert; + import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.components.ThumbnailView; import org.thoughtcrime.securesms.crypto.MasterSecret; diff --git a/src/org/thoughtcrime/securesms/mms/AudioSlide.java b/src/org/thoughtcrime/securesms/mms/AudioSlide.java index 78948e65c2..ef4850a7dd 100644 --- a/src/org/thoughtcrime/securesms/mms/AudioSlide.java +++ b/src/org/thoughtcrime/securesms/mms/AudioSlide.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources.Theme; import android.net.Uri; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ResUtil; @@ -49,6 +50,10 @@ public class AudioSlide extends Slide { return true; } + @NonNull @Override public String getContentDescription() { + return context.getString(R.string.Slide_audio); + } + @Override public @DrawableRes int getPlaceholderRes(Theme theme) { return ResUtil.getDrawableRes(theme, R.attr.conversation_icon_attach_audio); diff --git a/src/org/thoughtcrime/securesms/mms/ImageSlide.java b/src/org/thoughtcrime/securesms/mms/ImageSlide.java index 5720a53074..875943dc3a 100644 --- a/src/org/thoughtcrime/securesms/mms/ImageSlide.java +++ b/src/org/thoughtcrime/securesms/mms/ImageSlide.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources.Theme; import android.net.Uri; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; @@ -59,4 +60,8 @@ public class ImageSlide extends Slide { public boolean hasImage() { return true; } + + @NonNull @Override public String getContentDescription() { + return context.getString(R.string.Slide_image); + } } diff --git a/src/org/thoughtcrime/securesms/mms/Slide.java b/src/org/thoughtcrime/securesms/mms/Slide.java index f23ff2e653..2dd09a136f 100644 --- a/src/org/thoughtcrime/securesms/mms/Slide.java +++ b/src/org/thoughtcrime/securesms/mms/Slide.java @@ -63,6 +63,8 @@ public abstract class Slide { return false; } + public @NonNull String getContentDescription() { return ""; } + public PduPart getPart() { return part; } diff --git a/src/org/thoughtcrime/securesms/mms/VideoSlide.java b/src/org/thoughtcrime/securesms/mms/VideoSlide.java index 7140a3f795..d243d0c307 100644 --- a/src/org/thoughtcrime/securesms/mms/VideoSlide.java +++ b/src/org/thoughtcrime/securesms/mms/VideoSlide.java @@ -20,6 +20,7 @@ import android.content.Context; import android.content.res.Resources.Theme; import android.net.Uri; import android.support.annotation.DrawableRes; +import android.support.annotation.NonNull; import org.thoughtcrime.securesms.R; import org.thoughtcrime.securesms.util.ResUtil; @@ -53,4 +54,8 @@ public class VideoSlide extends Slide { public boolean hasVideo() { return true; } + + @NonNull @Override public String getContentDescription() { + return context.getString(R.string.Slide_video); + } } diff --git a/src/org/thoughtcrime/securesms/util/ViewUtil.java b/src/org/thoughtcrime/securesms/util/ViewUtil.java index 9f82db6625..130ec2b7ff 100644 --- a/src/org/thoughtcrime/securesms/util/ViewUtil.java +++ b/src/org/thoughtcrime/securesms/util/ViewUtil.java @@ -26,6 +26,7 @@ import android.text.TextUtils.TruncateAt; import android.view.View; import android.view.ViewGroup; import android.view.ViewStub; +import android.view.animation.Animation; import android.widget.TextView; public class ViewUtil { @@ -73,4 +74,26 @@ public class ViewUtil { public static T findById(@NonNull View parent, @IdRes int resId) { return (T) parent.findViewById(resId); } + + public static void animateOut(final @NonNull View view, final @NonNull Animation animation) { + if (view.getVisibility() == View.GONE) return; + + view.clearAnimation(); + animation.setAnimationListener(new Animation.AnimationListener() { + @Override public void onAnimationStart(Animation animation) {} + @Override public void onAnimationRepeat(Animation animation) {} + @Override public void onAnimationEnd(Animation animation) { + view.setVisibility(View.GONE); + } + }); + + view.startAnimation(animation); + } + + public static void animateIn(final @NonNull View view, final @NonNull Animation animation) { + if (view.getVisibility() == View.VISIBLE) return; + view.clearAnimation(); + view.setVisibility(View.VISIBLE); + view.startAnimation(animation); + } }