support message header collapsing
* toggle header collapse/expand
* initially render read messages collapsed
* collapse removes attachments footer and stops loader
* expand creates/reuses attachments footer, but ONLY if visible
* can (mostly) expand/collapse details
* can expand/collapse quoted text
* add lots of logging (disabled during scrolling!) to catch
strange layouts
Change-Id: I73f533c91d24ef1c05919d0a3b396f276898107a
diff --git a/assets/script.js b/assets/script.js
index a68db32..da380e6 100644
--- a/assets/script.js
+++ b/assets/script.js
@@ -46,6 +46,7 @@
var isHidden = getComputedStyle(elidedTextElement).display == 'none';
toggleElement.innerHTML = isHidden ? MSG_HIDE_ELIDED : MSG_SHOW_ELIDED;
elidedTextElement.style.display = isHidden ? 'block' : 'none';
+ measurePositions();
}
function collapseQuotedText() {
@@ -166,6 +167,32 @@
}
}
}
+
+function setMessageHeaderSpacerHeight(messageDomId, spacerHeight) {
+ var spacer = document.querySelector("#" + messageDomId + " > .mail-message-header");
+ if (!spacer) {
+ console.log("can't set spacer for message with id: " + messageDomId);
+ return;
+ }
+ spacer.style.height = spacerHeight + "px";
+ measurePositions();
+}
+
+function setMessageBodyVisible(messageDomId, isVisible, spacerHeight) {
+ var i, len;
+ var visibility = isVisible ? "block" : "none";
+ var messageDiv = document.querySelector("#" + messageDomId);
+ var collapsibleDivs = document.querySelectorAll("#" + messageDomId + " > .collapsible");
+ if (!messageDiv || collapsibleDivs.length == 0) {
+ console.log("can't set body visibility for message with id: " + messageDomId);
+ return;
+ }
+ messageDiv.classList.toggle("expanded");
+ for (i = 0, len = collapsibleDivs.length; i < len; i++) {
+ collapsibleDivs[i].style.display = visibility;
+ }
+ setMessageHeaderSpacerHeight(messageDomId, spacerHeight);
+}
// END Java->JavaScript handlers
collapseQuotedText();
diff --git a/res/layout/conversation_message_footer.xml b/res/layout/conversation_message_footer.xml
index ad675d3..82649e7 100644
--- a/res/layout/conversation_message_footer.xml
+++ b/res/layout/conversation_message_footer.xml
@@ -22,6 +22,5 @@
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
- android:paddingRight="16dp"
- android:paddingBottom="8dp">
+ android:paddingRight="16dp">
</com.android.mail.browse.MessageFooterView>
diff --git a/res/raw/template_message.html b/res/raw/template_message.html
index 1b75b10..69a25b0 100644
--- a/res/raw/template_message.html
+++ b/res/raw/template_message.html
@@ -1,5 +1,5 @@
<div id="%s" serverId="%s" class="mail-message %s">
<div class="mail-message-header spacer" style="height: %spx;"></div>
- <div class="mail-message-content %s" style="display: %s; zoom: %s; padding: 16px;">%s</div>
- <div class="mail-message-footer" style="height: %spx;"></div>
+ <div class="mail-message-content collapsible %s" style="display: %s; zoom: %s; padding: 16px;">%s</div>
+ <div class="mail-message-footer collapsible" style="display: %s; height: %spx;"></div>
</div>
diff --git a/src/com/android/mail/browse/ConversationContainer.java b/src/com/android/mail/browse/ConversationContainer.java
index 16863cf..1aeb4c6 100644
--- a/src/com/android/mail/browse/ConversationContainer.java
+++ b/src/com/android/mail/browse/ConversationContainer.java
@@ -128,6 +128,8 @@
private int mWidthMeasureSpec;
+ private boolean mDisableLayoutTracing;
+
private static final int VIEW_TAG_CONVERSATION_INDEX = R.id.view_tag_conversation_index;
/**
@@ -263,7 +265,9 @@
@Override
public void onNotifierScroll(final int x, final int y) {
+ mDisableLayoutTracing = true;
positionOverlays(x, y);
+ mDisableLayoutTracing = false;
}
private void positionOverlays(int x, int y) {
@@ -279,8 +283,8 @@
if (mTouchInitialized) {
mScale = mWebView.getScale();
}
- LogUtils.v(TAG, "in positionOverlays, raw scale=%f, effective scale=%f",
- mWebView.getScale(), mScale);
+ traceLayout("in positionOverlays, raw scale=%f, effective scale=%f", mWebView.getScale(),
+ mScale);
if (mOverlayBottoms == null) {
return;
@@ -294,8 +298,13 @@
// in a single stack until you encounter a non-contiguous expanded message header,
// then decrement to the next spacer.
+ traceLayout("IN positionOverlays, spacerCount=%d overlayCount=%d", mOverlayBottoms.length,
+ mOverlayAdapter.getCount());
+
int adapterIndex = mOverlayAdapter.getCount() - 1;
- for (int spacerIndex = mOverlayBottoms.length - 1; spacerIndex >= 0; spacerIndex--) {
+ int spacerIndex = mOverlayBottoms.length - 1;
+ while (spacerIndex >= 0 && adapterIndex >= 0) {
+
final int spacerBottomY = getOverlayBottom(spacerIndex);
// always place at least one overlay per spacer
@@ -304,6 +313,8 @@
int overlayBottomY = spacerBottomY;
int overlayTopY = overlayBottomY - adapterItem.getHeight();
+ traceLayout("in loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex, adapterIndex,
+ overlayTopY, overlayBottomY, adapterItem);
positionOverlay(adapterIndex, overlayTopY, overlayBottomY);
// and keep stacking overlays as long as they are contiguous
@@ -317,8 +328,12 @@
overlayBottomY = overlayTopY; // stack on top of previous overlay
overlayTopY = overlayBottomY - adapterItem.getHeight();
+ traceLayout("in contig loop, spacer=%d overlay=%d t/b=%d/%d (%s)", spacerIndex,
+ adapterIndex, overlayTopY, overlayBottomY, adapterItem);
positionOverlay(adapterIndex, overlayTopY, overlayBottomY);
}
+
+ spacerIndex--;
}
}
@@ -326,6 +341,10 @@
* Copied/stolen from {@link ListView}.
*/
private void measureItem(View child) {
+ if (child.getVisibility() == GONE) {
+ return;
+ }
+
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(
@@ -346,7 +365,7 @@
}
private void onOverlayScrolledOff(final View overlayView, final int itemType,
- int overlayTop) {
+ int overlayTop, int overlayBottom) {
// do it asynchronously, as scroll notification can happen during a draw, when it's not
// safe to remove children
@@ -366,7 +385,7 @@
// push it out of view immediately
// otherwise this scrolled-off header will continue to draw until the runnable runs
- layoutOverlay(overlayView, overlayTop);
+ layoutOverlay(overlayView, overlayTop, overlayBottom);
}
public View getScrapView(int type) {
@@ -402,7 +421,7 @@
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- LogUtils.d(TAG, "*** IN header container onMeasure spec for w/h=%d/%d", widthMeasureSpec,
+ LogUtils.i(TAG, "*** IN header container onMeasure spec for w/h=%d/%d", widthMeasureSpec,
heightMeasureSpec);
if (mWebView.getVisibility() != GONE) {
@@ -422,7 +441,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- LogUtils.d(TAG, "*** IN header container onLayout");
+ LogUtils.i(TAG, "*** IN header container onLayout");
mWebView.layout(0, 0, mWebView.getMeasuredWidth(), mWebView.getMeasuredHeight());
positionOverlays(0, mOffsetY);
@@ -436,27 +455,42 @@
private void positionOverlay(int adapterIndex, int overlayTopY, int overlayBottomY) {
View overlayView = findExistingOverlayView(adapterIndex);
final int itemType = mOverlayAdapter.getItemViewType(adapterIndex);
- // is the overlay visible?
- if (overlayBottomY > mOffsetY && overlayTopY < mOffsetY + getHeight()) {
+ // is the overlay visible and does it have non-zero height?
+ if (overlayTopY != overlayBottomY && overlayBottomY > mOffsetY
+ && overlayTopY < mOffsetY + getHeight()) {
// show and/or move overlay
if (overlayView == null) {
overlayView = addOverlayView(adapterIndex);
measureItem(overlayView);
+ traceLayout("show overlay %d", adapterIndex);
+ } else {
+ traceLayout("move overlay %d", adapterIndex);
}
layoutOverlay(overlayView, overlayTopY);
} else {
// hide overlay
if (overlayView != null) {
- onOverlayScrolledOff(overlayView, itemType, overlayTopY);
+ traceLayout("hide overlay %d", adapterIndex);
+ onOverlayScrolledOff(overlayView, itemType, overlayTopY, overlayBottomY);
+ } else {
+ traceLayout("ignore non-visible overlay %d", adapterIndex);
}
}
}
+ private void layoutOverlay(View child, int childTop) {
+ layoutOverlay(child, childTop, childTop + child.getMeasuredHeight());
+ }
+
// layout an existing view
// need its top offset into the conversation, its height, and the scroll offset
- private void layoutOverlay(View child, int childTop) {
+ private void layoutOverlay(View child, int childTop, int childBottom) {
+ if (child.getVisibility() == GONE) {
+ return;
+ }
+
final int top = childTop - mOffsetY;
- final int bottom = top + child.getMeasuredHeight();
+ final int bottom = childBottom - mOffsetY;
child.layout(0, top, child.getMeasuredWidth(), bottom);
}
@@ -471,12 +505,10 @@
// Since external components can contribute to the scrap heap (addScrapView), we can't
// assume scrap views had already been attached.
if (view.getRootView() != view) {
- LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s",
- adapterIndex, view);
+ LogUtils.d(TAG, "want to REUSE scrolled-in view: index=%d obj=%s", adapterIndex, view);
attachViewToParent(view, -1, view.getLayoutParams());
} else {
- LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s",
- adapterIndex, view);
+ LogUtils.d(TAG, "want to CREATE scrolled-in view: index=%d obj=%s", adapterIndex, view);
addViewInLayout(view, -1, view.getLayoutParams(),
true /* preventRequestLayout */);
}
@@ -488,22 +520,43 @@
for (int i = 0, count = getOverlayCount(); i < count; i++) {
final View overlay = getOverlayAt(i);
final Integer tag = (Integer) overlay.getTag(VIEW_TAG_CONVERSATION_INDEX);
- if (tag != null && tag == adapterIndex) {
+ // ignore children queued to be removed
+ // otherwise we'll re-use and lay out this view and then just throw it away
+ if (tag != null && tag == adapterIndex && !mChildrenToRemove.contains(overlay)) {
return overlay;
}
}
return null;
}
+ /**
+ * Prevents any layouts from happening until the next time {@link #onGeometryChange(int[])} is
+ * called. Useful when you know the HTML spacer coordinates are inconsistent with adapter items.
+ * <p>
+ * If you call this, you must ensure that a followup call to {@link #onGeometryChange(int[])}
+ * is made later, when the HTML spacer coordinates are updated.
+ *
+ */
+ public void invalidateSpacerGeometry() {
+ mOverlayBottoms = null;
+ }
+
// TODO: add margin support for children that want it (e.g. tablet headers?)
public void onGeometryChange(int[] overlayBottoms) {
- LogUtils.d(TAG, "*** got overlay spacer bottoms:");
+ traceLayout("*** got overlay spacer bottoms:");
for (int offsetY : overlayBottoms) {
- LogUtils.d(TAG, "%d", offsetY);
+ traceLayout("%d", offsetY);
}
mOverlayBottoms = overlayBottoms;
positionOverlays(0, mOffsetY);
}
+ private void traceLayout(String msg, Object... params) {
+ if (mDisableLayoutTracing) {
+ return;
+ }
+ LogUtils.i(TAG, msg, params);
+ }
+
}
diff --git a/src/com/android/mail/browse/ConversationViewAdapter.java b/src/com/android/mail/browse/ConversationViewAdapter.java
index eb0e215..282e39e 100644
--- a/src/com/android/mail/browse/ConversationViewAdapter.java
+++ b/src/com/android/mail/browse/ConversationViewAdapter.java
@@ -17,8 +17,6 @@
package com.android.mail.browse;
-import com.google.common.collect.Lists;
-
import android.app.LoaderManager;
import android.content.Context;
import android.view.LayoutInflater;
@@ -36,7 +34,9 @@
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Message;
import com.android.mail.providers.UIProvider;
+import com.android.mail.utils.LogUtils;
import com.android.mail.utils.Utils;
+import com.google.common.collect.Lists;
import java.util.List;
@@ -60,6 +60,7 @@
private final MessageHeaderViewCallbacks mMessageCallbacks;
private ConversationViewHeaderCallbacks mConversationCallbacks;
private final LayoutInflater mInflater;
+ private boolean mDefaultReplyAll;
private final List<ConversationItem> mItems;
@@ -68,6 +69,8 @@
public static final int VIEW_TYPE_MESSAGE_FOOTER = 2;
public static final int VIEW_TYPE_COUNT = 3;
+ public static final String LOG_TAG = new LogUtils().getLogTag();
+
public static abstract class ConversationItem {
private int mHeight; // in px
@@ -84,7 +87,6 @@
* @see CursorAdapter#bindView(View, Context, android.database.Cursor)
*/
public abstract void bindView(View v);
- public abstract int measureHeight(View v, ViewGroup parent);
/**
* Returns true if this overlay view is meant to be positioned right on top of the overlay
* below. This special positioning allows {@link ConversationContainer} to stack overlays
@@ -93,11 +95,31 @@
*/
public abstract boolean isContiguous();
+ /**
+ * Measure the expected visible height of the overlay view. Even if the view is initially
+ * GONE, this method must return whatever height the view is going to be when it is later
+ * made VISIBLE.
+ */
+ public int measureHeight(View v, ViewGroup parent) {
+ return Utils.measureViewHeight(v, parent);
+ }
+
+ /**
+ * This method's behavior is critical and requires some 'splainin.
+ * <p>
+ * Subclasses that return a zero-size height to the {@link ConversationContainer} will
+ * cause the scrolling/recycling logic there to remove any matching view from the container.
+ * The item should switch to returning a non-zero height when its view should re-appear.
+ * <p>
+ * It's imperative that this method stay in sync with the current height of the HTML spacer
+ * that matches this overlay.
+ */
public int getHeight() {
return mHeight;
}
public void setHeight(int h) {
+ LogUtils.i(LOG_TAG, "IN setHeight=%dpx of overlay item: %s", h, this);
mHeight = h;
}
}
@@ -135,11 +157,6 @@
}
@Override
- public int measureHeight(View v, ViewGroup parent) {
- return Utils.measureViewHeight(v, parent);
- }
-
- @Override
public boolean isContiguous() {
return true;
}
@@ -148,13 +165,14 @@
public class MessageHeaderItem extends ConversationItem {
public final Message message;
- public boolean expanded;
- public boolean defaultReplyAll;
+ private boolean mExpanded;
+ public boolean detailsExpanded;
- private MessageHeaderItem(Message message, boolean defaultReplyAll, boolean expanded) {
+ private MessageHeaderItem(Message message, boolean expanded) {
this.message = message;
- this.expanded = expanded;
- this.defaultReplyAll = defaultReplyAll;
+ mExpanded = expanded;
+
+ detailsExpanded = false;
}
@Override
@@ -166,7 +184,7 @@
public View createView(Context context, LayoutInflater inflater, ViewGroup parent) {
final MessageHeaderView v = (MessageHeaderView) inflater.inflate(
R.layout.conversation_message_header, parent, false);
- v.initialize(mDateBuilder, mAccount, defaultReplyAll /* defaultReplyAll */);
+ v.initialize(mDateBuilder, mAccount);
v.setCallbacks(mMessageCallbacks);
return v;
}
@@ -174,18 +192,22 @@
@Override
public void bindView(View v) {
final MessageHeaderView header = (MessageHeaderView) v;
- header.bind(message, expanded, message.shouldShowImagePrompt());
- }
-
- @Override
- public int measureHeight(View v, ViewGroup parent) {
- final MessageHeaderView header = (MessageHeaderView) v;
- return header.measureHeight(parent);
+ header.bind(this, mDefaultReplyAll);
}
@Override
public boolean isContiguous() {
- return !expanded;
+ return !isExpanded();
+ }
+
+ public boolean isExpanded() {
+ return mExpanded;
+ }
+
+ public void setExpanded(boolean expanded) {
+ if (mExpanded != expanded) {
+ mExpanded = expanded;
+ }
}
}
@@ -216,18 +238,23 @@
@Override
public void bindView(View v) {
final MessageFooterView attachmentsView = (MessageFooterView) v;
- attachmentsView.bind(headerItem.message, headerItem.expanded);
- }
-
- @Override
- public int measureHeight(View v, ViewGroup parent) {
- return Utils.measureViewHeight(v, parent);
+ attachmentsView.bind(headerItem);
}
@Override
public boolean isContiguous() {
return true;
}
+
+ @Override
+ public int getHeight() {
+ // a footer may change height while its view does not exist because it is offscreen
+ // (but the header is onscreen and thus collapsible)
+ if (!headerItem.isExpanded()) {
+ return 0;
+ }
+ return super.getHeight();
+ }
}
public ConversationViewAdapter(Context context, Account account, LoaderManager loaderManager,
@@ -244,6 +271,10 @@
mItems = Lists.newArrayList();
}
+ public void setDefaultReplyAll(boolean defaultReplyAll) {
+ mDefaultReplyAll = defaultReplyAll;
+ }
+
@Override
public int getCount() {
return mItems.size();
@@ -300,8 +331,8 @@
return addItem(new ConversationHeaderItem(conv));
}
- public int addMessageHeader(Message msg, boolean defaultReplyAll, boolean expanded) {
- return addItem(new MessageHeaderItem(msg, defaultReplyAll, expanded));
+ public int addMessageHeader(Message msg, boolean expanded) {
+ return addItem(new MessageHeaderItem(msg, expanded));
}
public int addMessageFooter(MessageHeaderItem headerItem) {
diff --git a/src/com/android/mail/browse/MessageFooterView.java b/src/com/android/mail/browse/MessageFooterView.java
index fa86e12..39a459e 100644
--- a/src/com/android/mail/browse/MessageFooterView.java
+++ b/src/com/android/mail/browse/MessageFooterView.java
@@ -17,8 +17,6 @@
package com.android.mail.browse;
-import com.google.common.collect.Lists;
-
import android.app.LoaderManager;
import android.content.Context;
import android.content.Loader;
@@ -30,19 +28,20 @@
import com.android.mail.browse.AttachmentLoader.AttachmentCursor;
import com.android.mail.browse.ConversationContainer.DetachListener;
+import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
import com.android.mail.providers.Attachment;
import com.android.mail.providers.Message;
import com.android.mail.utils.LogUtils;
+import com.google.common.collect.Lists;
import java.util.List;
public class MessageFooterView extends LinearLayout implements DetachListener,
LoaderManager.LoaderCallbacks<Cursor> {
- private Message mMessage;
+ private MessageHeaderItem mMessageHeaderItem;
private LoaderManager mLoaderManager;
private AttachmentCursor mAttachmentsCursor;
- private boolean mIsExpanded;
private LayoutInflater mInflater;
/**
@@ -82,45 +81,41 @@
mLoaderManager = loaderManager;
}
- public void bind(Message msg, boolean expanded) {
- mMessage = msg;
- mIsExpanded = expanded;
+ public void bind(MessageHeaderItem headerItem) {
+ mMessageHeaderItem = headerItem;
+
+ /*
+ * Assuming ConversationContainer does not requesting adapter views for zero-height items,
+ * we should just always render even if the matching header is collapsed.
+ */
removeAllViewsInLayout();
// kick off load of Attachment objects in background thread
final Integer attachmentLoaderId = getAttachmentLoaderId();
if (sEnableAttachmentLoaders && attachmentLoaderId != null) {
- LogUtils.d(LOG_TAG, "binding footer view, calling initLoader for message %d",
+ LogUtils.i(LOG_TAG, "binding footer view, calling initLoader for message %d",
attachmentLoaderId);
mLoaderManager.initLoader(attachmentLoaderId, Bundle.EMPTY, this);
}
- if (mIsExpanded) {
- setVisibility(VISIBLE);
- // Do an initial render if initLoader didn't already do one
- if (getChildCount() == 0) {
- renderAttachments();
- }
- } else {
- setVisibility(GONE);
+ // Do an initial render if initLoader didn't already do one
+ if (getChildCount() == 0) {
+ renderAttachments();
}
+ setVisibility(mMessageHeaderItem.isExpanded() ? VISIBLE : GONE);
}
- private void destroyLoader() {
+ private void unbind() {
final Integer loaderId = getAttachmentLoaderId();
if (mLoaderManager != null && loaderId != null) {
- LogUtils.d(LOG_TAG, "detaching/reusing footer view,"
- + " calling destroyLoader for message %d", loaderId);
+ LogUtils.i(LOG_TAG, "detaching footer view, calling destroyLoader for message %d",
+ loaderId);
mLoaderManager.destroyLoader(loaderId);
}
}
private void renderAttachments() {
- if (!mIsExpanded) {
- return;
- }
-
List<Attachment> attachments;
if (mAttachmentsCursor != null && !mAttachmentsCursor.isClosed()) {
int i = -1;
@@ -131,7 +126,7 @@
} else {
// before the attachment loader results are in, we can still render immediately using
// the basic info in the message's attachmentsJSON
- attachments = mMessage.getAttachments();
+ attachments = mMessageHeaderItem.message.getAttachments();
}
renderAttachments(attachments);
}
@@ -153,8 +148,9 @@
private Integer getAttachmentLoaderId() {
Integer id = null;
- if (mMessage != null && mMessage.hasAttachments && mMessage.attachmentListUri != null) {
- id = mMessage.attachmentListUri.hashCode();
+ final Message msg = mMessageHeaderItem == null ? null : mMessageHeaderItem.message;
+ if (msg != null && msg.hasAttachments && msg.attachmentListUri != null) {
+ id = msg.attachmentListUri.hashCode();
}
return id;
}
@@ -162,17 +158,17 @@
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- destroyLoader();
+ unbind();
}
@Override
public void onDetachedFromParent() {
- destroyLoader();
+ unbind();
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
- return new AttachmentLoader(getContext(), mMessage.attachmentListUri);
+ return new AttachmentLoader(getContext(), mMessageHeaderItem.message.attachmentListUri);
}
@Override
diff --git a/src/com/android/mail/browse/MessageHeaderView.java b/src/com/android/mail/browse/MessageHeaderView.java
index 0e2cc5d..8187942 100644
--- a/src/com/android/mail/browse/MessageHeaderView.java
+++ b/src/com/android/mail/browse/MessageHeaderView.java
@@ -43,6 +43,7 @@
import com.android.mail.FormattedDateBuilder;
import com.android.mail.R;
import com.android.mail.SenderInfoLoader.ContactInfo;
+import com.android.mail.browse.ConversationViewAdapter.MessageHeaderItem;
import com.android.mail.compose.ComposeActivity;
import com.android.mail.perf.Timer;
import com.android.mail.providers.Account;
@@ -81,7 +82,6 @@
private static final String LOG_TAG = new LogUtils().getLogTag();
private MessageHeaderViewCallbacks mCallbacks;
- private boolean mSizeChanged;
private TextView mSenderNameView;
private TextView mSenderEmailView;
@@ -107,8 +107,6 @@
private boolean mIsSending;
- private boolean mIsExpanded;
-
private boolean mDetailsExpanded;
/**
@@ -152,6 +150,7 @@
private PopupMenu mPopup;
+ private MessageHeaderItem mMessageHeaderItem;
private Message mMessage;
private boolean mCollapsedDetailsValid;
@@ -162,10 +161,9 @@
private AsyncQueryHandler mQueryHandler;
public interface MessageHeaderViewCallbacks {
- void setMessageSpacerHeight(Message msg, int height);
+ void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeight);
- void setMessageExpanded(Message msg, boolean expanded,
- int spacerHeight);
+ void setMessageExpanded(MessageHeaderItem item, int newSpacerHeight);
void showExternalResources(Message msg);
}
@@ -237,7 +235,8 @@
}
public boolean isExpanded() {
- return mIsExpanded;
+ // (let's just arbitrarily say that unbound views are expanded by default)
+ return mMessageHeaderItem == null || mMessageHeaderItem.isExpanded();
}
@Override
@@ -277,10 +276,12 @@
* renderUpperHeaderFrom().
*/
public void unbind() {
+ mMessageHeaderItem = null;
mMessage = null;
}
public void renderUpperHeaderFrom(MessageHeaderView other) {
+ mMessageHeaderItem = other.mMessageHeaderItem;
mMessage = other.mMessage;
mSender = other.mSender;
mDefaultReplyAll = other.mDefaultReplyAll;
@@ -297,23 +298,23 @@
updateChildVisibility();
}
- public void initialize(FormattedDateBuilder dateBuilder, Account account,
- boolean defaultReplyAll) {
+ public void initialize(FormattedDateBuilder dateBuilder, Account account) {
mDateBuilder = dateBuilder;
mAccount = account;
- mDefaultReplyAll = defaultReplyAll;
}
- public void bind(Message message, boolean expanded, boolean showImagePrompt) {
+ public void bind(MessageHeaderItem headerItem, boolean defaultReplyAll) {
Timer t = new Timer();
t.start(HEADER_RENDER_TAG);
mCollapsedDetailsValid = false;
mExpandedDetailsValid = false;
- mMessage = message;
- setExpanded(expanded);
- mShowImagePrompt = showImagePrompt;
+ mMessageHeaderItem = headerItem;
+ mMessage = headerItem.message;
+ mShowImagePrompt = mMessage.shouldShowImagePrompt();
+ mDefaultReplyAll = defaultReplyAll;
+ setExpanded(headerItem.isExpanded());
mTimestampMs = mMessage.dateReceivedMs;
if (mDateBuilder != null) {
@@ -376,12 +377,23 @@
return false;
}
- public int measureHeight(ViewGroup parent) {
+ private void updateSpacerHeight() {
+ final int h = measureHeight();
+
+ mMessageHeaderItem.setHeight(h);
+ if (mCallbacks != null) {
+ mCallbacks.setMessageSpacerHeight(mMessageHeaderItem, h);
+ }
+ }
+
+ private int measureHeight() {
+ ViewGroup parent = (ViewGroup) getParent();
if (parent == null) {
+ LogUtils.e(LOG_TAG, new Error(), "Unable to measure height of detached header");
return getHeight();
}
mPreMeasuring = true;
- int h = Utils.measureViewHeight(this, parent);
+ final int h = Utils.measureViewHeight(this, parent);
mPreMeasuring = false;
return h;
}
@@ -405,7 +417,7 @@
if (mIsSending) {
sub = null;
} else {
- sub = mIsExpanded ? getSenderAddress(mSender) : mSnippet;
+ sub = isExpanded() ? getSenderAddress(mSender) : mSnippet;
}
return sub;
}
@@ -439,7 +451,9 @@
// use View's 'activated' flag to store expanded state
// child view state lists can use this to toggle drawables
setActivated(expanded);
- mIsExpanded = expanded;
+ if (mMessageHeaderItem != null) {
+ mMessageHeaderItem.setExpanded(expanded);
+ }
}
/**
@@ -449,7 +463,7 @@
private void updateChildVisibility() {
// Too bad this can't be done with an XML state list...
- if (mIsExpanded) {
+ if (isExpanded()) {
int normalVis, draftVis;
setMessageDetailsVisibility((mIsSnappy) ? GONE : VISIBLE);
@@ -783,7 +797,7 @@
return;
}
- setExpanded(!mIsExpanded);
+ setExpanded(!isExpanded());
mSenderNameView.setText(getHeaderTitle());
mSenderEmailView.setText(getHeaderSubtitle());
@@ -794,15 +808,17 @@
// reveal the message
// div in one pass. Force-measuring makes it unnecessary to set
// mSizeChanged.
- int h = measureHeight((ViewGroup) getParent());
+ int h = measureHeight();
+ mMessageHeaderItem.setHeight(h);
if (mCallbacks != null) {
- mCallbacks.setMessageExpanded(mMessage, mIsExpanded, h);
+ mCallbacks.setMessageExpanded(mMessageHeaderItem, h);
}
}
private void toggleMessageDetails(View visibleDetailsView) {
- setMessageDetailsExpanded(visibleDetailsView == mCollapsedDetailsView);
- mSizeChanged = true;
+ final boolean detailsExpanded = (visibleDetailsView == mCollapsedDetailsView);
+ setMessageDetailsExpanded(detailsExpanded);
+ updateSpacerHeight();
}
private void setMessageDetailsExpanded(boolean expand) {
@@ -813,7 +829,9 @@
hideExpandedDetails();
showCollapsedDetails();
}
- mDetailsExpanded = expand;
+ if (mMessageHeaderItem != null) {
+ mMessageHeaderItem.detailsExpanded = expand;
+ }
}
public void setMessageDetailsVisibility(int vis) {
@@ -821,23 +839,13 @@
hideCollapsedDetails();
hideExpandedDetails();
hideShowImagePrompt();
- // FIXME: coordinate with matching footer (if exists) to show/hide
- // hideAttachments();
} else {
- setMessageDetailsExpanded(mDetailsExpanded);
+ setMessageDetailsExpanded(mMessageHeaderItem.detailsExpanded);
if (mShowImagePrompt) {
showImagePrompt();
} else {
hideShowImagePrompt();
}
- // FIXME: coordinate with matching footer (if exists) to show/hide
- /*
- if (mMessage.hasAttachments) {
- showAttachments();
- } else {
- hideAttachments();
- }
- */
}
if (mBottomBorderView != null) {
mBottomBorderView.setVisibility(vis);
@@ -902,10 +910,8 @@
TextView descriptionView = (TextView) v.findViewById(R.id.show_pictures_text);
descriptionView.setText(R.string.always_show_images);
v.setTag(SHOW_IMAGE_PROMPT_ALWAYS);
- // the new text's line count may differ, which should trigger a
- // size change to
- // update the spacer height
- mSizeChanged = true;
+ // the new text's line count may differ, so update the spacer height
+ updateSpacerHeight();
break;
case SHOW_IMAGE_PROMPT_ALWAYS:
mMessage.markAlwaysShowImages(getQueryHandler(), 0 /* token */, null /* cookie */);
@@ -913,7 +919,7 @@
mShowImagePrompt = false;
v.setTag(null);
v.setVisibility(GONE);
- mSizeChanged = true;
+ updateSpacerHeight();
Toast.makeText(getContext(), R.string.always_show_images_toast, Toast.LENGTH_SHORT)
.show();
break;
@@ -996,21 +1002,6 @@
mExpandedDetailsView.setVisibility(VISIBLE);
}
- @Override
- protected void onSizeChanged(int w, int h, int oldw, int oldh) {
- super.onSizeChanged(w, h, oldw, oldh);
-
- if (mSizeChanged) {
- // propagate new size to webview header spacer
- // only do this for known size changes
- if (mCallbacks != null) {
- mCallbacks.setMessageSpacerHeight(mMessage, h);
- }
-
- mSizeChanged = false;
- }
- }
-
/**
* Returns a short plaintext snippet generated from the given HTML message
* body. Collapses whitespace, ignores '<' and '>' characters and
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 8898c00..1f3ba50 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -326,6 +326,11 @@
boolean allowNetworkImages = false;
+ // TODO: re-use any existing adapter item state (expanded, details expanded, show pics)
+
+ mAdapter.setDefaultReplyAll(mActivity.getSettings().replyBehavior ==
+ UIProvider.DefaultReplyBehavior.REPLY_ALL);
+
// Walk through the cursor and build up an overlay adapter as you go.
// Each overlay has an entry in the adapter for easy scroll handling in the container.
// Items are not necessarily 1:1 in cursor and adapter because of super-collapsed blocks.
@@ -353,12 +358,9 @@
final boolean safeForImages = msg.alwaysShowImages /* || savedStateSaysSafe */;
allowNetworkImages |= safeForImages;
- final int headerPos = mAdapter
- .addMessageHeader(
- msg,
- (mActivity.getSettings().replyBehavior
- == UIProvider.DefaultReplyBehavior.REPLY_ALL),
- true /* expanded */);
+ final boolean expanded = !msg.read || msg.starred || messageCursor.isLast();
+
+ final int headerPos = mAdapter.addMessageHeader(msg, expanded);
final MessageHeaderItem headerItem = (MessageHeaderItem) mAdapter.getItem(headerPos);
final int footerPos = mAdapter.addMessageFooter(headerItem);
@@ -369,7 +371,7 @@
final int headerDp = measureOverlayHeight(headerPos);
final int footerDp = measureOverlayHeight(footerPos);
- mTemplates.appendMessageHtml(msg, true /* expanded */, safeForImages, 1.0f, headerDp,
+ mTemplates.appendMessageHtml(msg, expanded, safeForImages, 1.0f, headerDp,
footerDp);
}
@@ -439,14 +441,26 @@
// START message header callbacks
@Override
- public void setMessageSpacerHeight(Message msg, int height) {
- // TODO: update message HTML spacer height
- // TODO: expand this concept to handle bottom-aligned attachments
+ public void setMessageSpacerHeight(MessageHeaderItem item, int newSpacerHeightPx) {
+ mConversationContainer.invalidateSpacerGeometry();
+
+ // update message HTML spacer height
+ LogUtils.i(LOG_TAG, "setting HTML spacer h=%dpx", newSpacerHeightPx);
+ final int heightDp = (int) (newSpacerHeightPx / mDensity);
+ mWebView.loadUrl(String.format("javascript:setMessageHeaderSpacerHeight('%s', %d);",
+ mTemplates.getMessageDomId(item.message), heightDp));
}
@Override
- public void setMessageExpanded(Message msg, boolean expanded, int spacerHeight) {
- // TODO: show/hide the HTML message body and update the spacer height
+ public void setMessageExpanded(MessageHeaderItem item, int newSpacerHeightPx) {
+ mConversationContainer.invalidateSpacerGeometry();
+
+ // show/hide the HTML message body and update the spacer height
+ LogUtils.i(LOG_TAG, "setting HTML spacer expanded=%s h=%dpx", item.isExpanded(),
+ newSpacerHeightPx);
+ final int heightDp = (int) (newSpacerHeightPx / mDensity);
+ mWebView.loadUrl(String.format("javascript:setMessageBodyVisible('%s', %s, %d);",
+ mTemplates.getMessageDomId(item.message), item.isExpanded(), heightDp));
}
@Override
diff --git a/src/com/android/mail/ui/HtmlConversationTemplates.java b/src/com/android/mail/ui/HtmlConversationTemplates.java
index 24a96b4..7fae644 100644
--- a/src/com/android/mail/ui/HtmlConversationTemplates.java
+++ b/src/com/android/mail/ui/HtmlConversationTemplates.java
@@ -167,6 +167,7 @@
bodyDisplay,
zoomValue,
body,
+ bodyDisplay,
footerHeight
);
}