summaryrefslogtreecommitdiffstats
path: root/src/com/android/messaging/ui/AttachmentPreview.java
diff options
context:
space:
mode:
authorMike Dodd <mdodd@google.com>2015-08-11 11:16:59 -0700
committerMike Dodd <mdodd@google.com>2015-08-12 08:58:28 -0700
commit461a34b466cb4b13dbbc2ec6330b31e217b2ac4e (patch)
treebc4b489af52d0e2521e21167d2ad76a47256f348 /src/com/android/messaging/ui/AttachmentPreview.java
parent8b3e2b9c1b0a09423a7ba5d1091b9192106502f8 (diff)
downloadandroid_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.gz
android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.tar.bz2
android_packages_apps_Messaging-461a34b466cb4b13dbbc2ec6330b31e217b2ac4e.zip
Initial checkin of AOSP Messaging app.
b/23110861 Change-Id: I9aa980d7569247d6b2ca78f5dcb4502e1eaadb8a
Diffstat (limited to 'src/com/android/messaging/ui/AttachmentPreview.java')
-rw-r--r--src/com/android/messaging/ui/AttachmentPreview.java331
1 files changed, 331 insertions, 0 deletions
diff --git a/src/com/android/messaging/ui/AttachmentPreview.java b/src/com/android/messaging/ui/AttachmentPreview.java
new file mode 100644
index 0000000..7eea14b
--- /dev/null
+++ b/src/com/android/messaging/ui/AttachmentPreview.java
@@ -0,0 +1,331 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.messaging.ui;
+
+import android.animation.Animator;
+import android.animation.ObjectAnimator;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Looper;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.ScrollView;
+
+import com.android.messaging.R;
+import com.android.messaging.annotation.VisibleForAnimation;
+import com.android.messaging.datamodel.data.DraftMessageData;
+import com.android.messaging.datamodel.data.MediaPickerMessagePartData;
+import com.android.messaging.datamodel.data.MessagePartData;
+import com.android.messaging.datamodel.data.PendingAttachmentData;
+import com.android.messaging.ui.MultiAttachmentLayout.OnAttachmentClickListener;
+import com.android.messaging.ui.animation.PopupTransitionAnimation;
+import com.android.messaging.ui.conversation.ComposeMessageView;
+import com.android.messaging.ui.conversation.ConversationFragment;
+import com.android.messaging.util.Assert;
+import com.android.messaging.util.ThreadUtil;
+import com.android.messaging.util.UiUtils;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class AttachmentPreview extends ScrollView implements OnAttachmentClickListener {
+ private FrameLayout mAttachmentView;
+ private ComposeMessageView mComposeMessageView;
+ private ImageButton mCloseButton;
+ private int mAnimatedHeight = -1;
+ private Animator mCloseGapAnimator;
+ private boolean mPendingFirstUpdate;
+ private Handler mHandler;
+ private Runnable mHideRunnable;
+ private boolean mPendingHideCanceled;
+
+ private static final int CLOSE_BUTTON_REVEAL_STAGGER_MILLIS = 300;
+
+ public AttachmentPreview(final Context context, final AttributeSet attrs) {
+ super(context, attrs);
+ mHandler = new Handler(Looper.getMainLooper());
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ mCloseButton = (ImageButton) findViewById(R.id.close_button);
+ mCloseButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(final View view) {
+ mComposeMessageView.clearAttachments();
+ }
+ });
+
+ mAttachmentView = (FrameLayout) findViewById(R.id.attachment_view);
+
+ // The attachment preview is a scroll view so that it can show the bottom portion of the
+ // attachment whenever the space is tight (e.g. when in landscape mode). Per design
+ // request we'd like to make the attachment view always scrolled to the bottom.
+ addOnLayoutChangeListener(new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(final View v, final int left, final int top, final int right,
+ final int bottom, final int oldLeft, final int oldTop, final int oldRight,
+ final int oldBottom) {
+ post(new Runnable() {
+ @Override
+ public void run() {
+ final int childCount = getChildCount();
+ if (childCount > 0) {
+ final View lastChild = getChildAt(childCount - 1);
+ scrollTo(getScrollX(), lastChild.getBottom() - getHeight());
+ }
+ }
+ });
+ }
+ });
+ mPendingFirstUpdate = true;
+ }
+
+ public void setComposeMessageView(final ComposeMessageView composeMessageView) {
+ mComposeMessageView = composeMessageView;
+ }
+
+ @Override
+ protected void onMeasure(final int widthMeasureSpec, final int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ if (mAnimatedHeight >= 0) {
+ setMeasuredDimension(getMeasuredWidth(), mAnimatedHeight);
+ }
+ }
+
+ private void cancelPendingHide() {
+ mPendingHideCanceled = true;
+ }
+
+ public void hideAttachmentPreview() {
+ if (getVisibility() != GONE) {
+ UiUtils.revealOrHideViewWithAnimation(mCloseButton, GONE,
+ null /* onFinishRunnable */);
+ startCloseGapAnimationOnAttachmentClear();
+
+ if (mAttachmentView.getChildCount() > 0) {
+ mPendingHideCanceled = false;
+ final View viewToHide = mAttachmentView.getChildCount() > 1 ?
+ mAttachmentView : mAttachmentView.getChildAt(0);
+ UiUtils.revealOrHideViewWithAnimation(viewToHide, INVISIBLE,
+ new Runnable() {
+ @Override
+ public void run() {
+ // Only hide if we are didn't get overruled by showing
+ if (!mPendingHideCanceled) {
+ mAttachmentView.removeAllViews();
+ setVisibility(GONE);
+ }
+ }
+ });
+ } else {
+ mAttachmentView.removeAllViews();
+ setVisibility(GONE);
+ }
+ }
+ }
+
+ // returns true if we have attachments
+ public boolean onAttachmentsChanged(final DraftMessageData draftMessageData) {
+ final boolean isFirstUpdate = mPendingFirstUpdate;
+ final List<MessagePartData> attachments = draftMessageData.getReadOnlyAttachments();
+ final List<PendingAttachmentData> pendingAttachments =
+ draftMessageData.getReadOnlyPendingAttachments();
+
+ // Any change in attachments would invalidate the animated height animation.
+ cancelCloseGapAnimation();
+ mPendingFirstUpdate = false;
+
+ final int combinedAttachmentCount = attachments.size() + pendingAttachments.size();
+ mCloseButton.setContentDescription(getResources()
+ .getQuantityString(R.plurals.attachment_preview_close_content_description,
+ combinedAttachmentCount));
+ if (combinedAttachmentCount == 0) {
+ mHideRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mHideRunnable = null;
+ // Only start the hiding if there are still no attachments
+ if (attachments.size() + pendingAttachments.size() == 0) {
+ hideAttachmentPreview();
+ }
+ }
+ };
+ if (draftMessageData.isSending()) {
+ // Wait to hide until the message is ready to start animating
+ // We'll execute immediately when the animation triggers
+ mHandler.postDelayed(mHideRunnable,
+ ConversationFragment.MESSAGE_ANIMATION_MAX_WAIT);
+ } else {
+ // Run immediately when clearing attachments
+ mHideRunnable.run();
+ }
+ return false;
+ }
+
+ cancelPendingHide(); // We're showing
+ if (getVisibility() != VISIBLE) {
+ setVisibility(VISIBLE);
+ mAttachmentView.setVisibility(VISIBLE);
+
+ // Don't animate in the close button if this is the first update after view creation.
+ // This is the initial draft load from database for pre-existing drafts.
+ if (!isFirstUpdate) {
+ // Reveal the close button after the view animates in.
+ mCloseButton.setVisibility(INVISIBLE);
+ ThreadUtil.getMainThreadHandler().postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ UiUtils.revealOrHideViewWithAnimation(mCloseButton, VISIBLE,
+ null /* onFinishRunnable */);
+ }
+ }, UiUtils.MEDIAPICKER_TRANSITION_DURATION + CLOSE_BUTTON_REVEAL_STAGGER_MILLIS);
+ }
+ }
+
+ // Merge the pending attachment list with real attachment. Design would prefer these be
+ // in LIFO order user can see added images past the 5th one but we also want them to be in
+ // order and we want it to be WYSIWYG.
+ final List<MessagePartData> combinedAttachments = new ArrayList<>();
+ combinedAttachments.addAll(attachments);
+ combinedAttachments.addAll(pendingAttachments);
+
+ final LayoutInflater layoutInflater = LayoutInflater.from(getContext());
+ if (combinedAttachmentCount > 1) {
+ MultiAttachmentLayout multiAttachmentLayout = null;
+ Rect transitionRect = null;
+ if (mAttachmentView.getChildCount() > 0) {
+ final View firstChild = mAttachmentView.getChildAt(0);
+ if (firstChild instanceof MultiAttachmentLayout) {
+ Assert.equals(1, mAttachmentView.getChildCount());
+ multiAttachmentLayout = (MultiAttachmentLayout) firstChild;
+ multiAttachmentLayout.bindAttachments(combinedAttachments,
+ null /* transitionRect */, combinedAttachmentCount);
+ } else {
+ transitionRect = new Rect(firstChild.getLeft(), firstChild.getTop(),
+ firstChild.getRight(), firstChild.getBottom());
+ }
+ }
+ if (multiAttachmentLayout == null) {
+ multiAttachmentLayout = AttachmentPreviewFactory.createMultiplePreview(
+ getContext(), this);
+ multiAttachmentLayout.bindAttachments(combinedAttachments, transitionRect,
+ combinedAttachmentCount);
+ mAttachmentView.removeAllViews();
+ mAttachmentView.addView(multiAttachmentLayout);
+ }
+ } else {
+ final MessagePartData attachment = combinedAttachments.get(0);
+ boolean shouldAnimate = true;
+ if (mAttachmentView.getChildCount() > 0) {
+ // If we are going from N->1 attachments, try to use the current bounds
+ // bounds as the starting rect.
+ shouldAnimate = false;
+ final View firstChild = mAttachmentView.getChildAt(0);
+ if (firstChild instanceof MultiAttachmentLayout &&
+ attachment instanceof MediaPickerMessagePartData) {
+ final View leftoverView = ((MultiAttachmentLayout) firstChild)
+ .findViewForAttachment(attachment);
+ if (leftoverView != null) {
+ final Rect currentRect = UiUtils.getMeasuredBoundsOnScreen(leftoverView);
+ if (!currentRect.isEmpty() &&
+ attachment instanceof MediaPickerMessagePartData) {
+ ((MediaPickerMessagePartData) attachment).setStartRect(currentRect);
+ shouldAnimate = true;
+ }
+ }
+ }
+ }
+ mAttachmentView.removeAllViews();
+ final View attachmentView = AttachmentPreviewFactory.createAttachmentPreview(
+ layoutInflater, attachment, mAttachmentView,
+ AttachmentPreviewFactory.TYPE_SINGLE, true /* startImageRequest */, this);
+ if (attachmentView != null) {
+ mAttachmentView.addView(attachmentView);
+ if (shouldAnimate) {
+ tryAnimateViewIn(attachment, attachmentView);
+ }
+ }
+ }
+ return true;
+ }
+
+ public void onMessageAnimationStart() {
+ if (mHideRunnable == null) {
+ return;
+ }
+
+ // Run the hide animation at the same time as the message animation
+ mHandler.removeCallbacks(mHideRunnable);
+ setVisibility(View.INVISIBLE);
+ mHideRunnable.run();
+ }
+
+ static void tryAnimateViewIn(final MessagePartData attachmentData, final View view) {
+ if (attachmentData instanceof MediaPickerMessagePartData) {
+ final Rect startRect = ((MediaPickerMessagePartData) attachmentData).getStartRect();
+ new PopupTransitionAnimation(startRect, view).startAfterLayoutComplete();
+ }
+ }
+
+ @VisibleForAnimation
+ public void setAnimatedHeight(final int animatedHeight) {
+ if (mAnimatedHeight != animatedHeight) {
+ mAnimatedHeight = animatedHeight;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Kicks off an animation to animate the layout change for closing the gap between the
+ * message list and the compose message box when the attachments are cleared.
+ */
+ private void startCloseGapAnimationOnAttachmentClear() {
+ // Cancel existing animation.
+ cancelCloseGapAnimation();
+ mCloseGapAnimator = ObjectAnimator.ofInt(this, "animatedHeight", getHeight(), 0);
+ mCloseGapAnimator.start();
+ }
+
+ private void cancelCloseGapAnimation() {
+ if (mCloseGapAnimator != null) {
+ mCloseGapAnimator.cancel();
+ mCloseGapAnimator = null;
+ }
+ mAnimatedHeight = -1;
+ }
+
+ @Override
+ public boolean onAttachmentClick(final MessagePartData attachment,
+ final Rect viewBoundsOnScreen, final boolean longPress) {
+ if (longPress) {
+ mComposeMessageView.onAttachmentPreviewLongClicked();
+ return true;
+ }
+
+ if (!(attachment instanceof PendingAttachmentData) && attachment.isImage()) {
+ mComposeMessageView.displayPhoto(attachment.getContentUri(), viewBoundsOnScreen);
+ return true;
+ }
+ return false;
+ }
+}