332 lines
14 KiB
Java
332 lines
14 KiB
Java
/*
|
|
* 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;
|
|
}
|
|
}
|