"new message from" in conversation view on update
* put the New Message bar in a new floaty layer in
ConversationContainer. snap header can also live here.
* add left/right margin support to ConversationContainer
Send/save of a new message also generates the 'new message'
notification for now.
Bug: 6384217
Change-Id: I10a40bbf87423194214e5ded08539abaaf7fd25c
diff --git a/src/com/android/mail/ui/ConversationViewFragment.java b/src/com/android/mail/ui/ConversationViewFragment.java
index 70149c7..047c934 100644
--- a/src/com/android/mail/ui/ConversationViewFragment.java
+++ b/src/com/android/mail/ui/ConversationViewFragment.java
@@ -32,6 +32,7 @@
import android.os.Bundle;
import android.os.Handler;
import android.provider.Browser;
+import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -43,6 +44,7 @@
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
+import android.widget.TextView;
import com.android.mail.ContactInfo;
import com.android.mail.ContactInfoSource;
@@ -113,6 +115,8 @@
private ConversationWebView mWebView;
+ private View mNewMessageBar;
+
private HtmlConversationTemplates mTemplates;
private String mBaseUri;
@@ -125,6 +129,7 @@
private ConversationViewAdapter mAdapter;
private MessageCursor mCursor;
+ private MessageCursor mPendingCursor;
private boolean mViewsCreated;
@@ -173,6 +178,7 @@
private static final String BUNDLE_VIEW_STATE = "viewstate";
private static final boolean DEBUG_DUMP_CONVERSATION_HTML = false;
+ private static final boolean DISABLE_OFFSCREEN_LOADING = false;
/**
* Constructor needs to be public to handle orientation changes and activity lifecycle events.
@@ -264,6 +270,15 @@
View rootView = inflater.inflate(R.layout.conversation_view, container, false);
mConversationContainer = (ConversationContainer) rootView
.findViewById(R.id.conversation_container);
+
+ mNewMessageBar = mConversationContainer.findViewById(R.id.new_message_notification_bar);
+ mNewMessageBar.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ onNewMessageBarClick();
+ }
+ });
+
mWebView = (ConversationWebView) mConversationContainer.findViewById(R.id.webview);
mWebView.addJavascriptInterface(mJsBridge, "mail");
@@ -445,7 +460,9 @@
* a folder. This will initiate a data load, and hence must be called on the UI thread.
*/
private void showConversation() {
- if (!mUserVisible && mConversation.getNumMessages() > mMaxAutoLoadMessages) {
+ final boolean disableOffscreenLoading = DISABLE_OFFSCREEN_LOADING
+ || (mConversation.getNumMessages() > mMaxAutoLoadMessages);
+ if (!mUserVisible && disableOffscreenLoading) {
LogUtils.v(LOG_TAG, "Fragment not user-visible, not showing conversation: %s",
mConversation.uri);
mDeferredConversationLoad = true;
@@ -513,6 +530,11 @@
mAdapter.clear();
+ // re-evaluate the message parts of the view state, since the messages may have changed
+ // since the previous render
+ final ConversationViewState prevState = mViewState;
+ mViewState = new ConversationViewState(prevState);
+
// N.B. the units of height for spacers are actually dp and not px because WebView assumes
// a pixel is an mdpi pixel, unless you set device-dpi.
@@ -533,10 +555,11 @@
final boolean safeForImages = msg.alwaysShowImages /* || savedStateSaysSafe */;
allowNetworkImages |= safeForImages;
- final Boolean savedExpanded = mViewState.getExpandedState(msg);
+ final Boolean savedExpanded = prevState.getExpandedState(msg);
final boolean expanded;
if (savedExpanded != null) {
expanded = savedExpanded;
+ mViewState.setExpandedState(msg, expanded);
} else {
expanded = !msg.read || msg.starred || messageCursor.isLast();
}
@@ -772,6 +795,20 @@
mWebView.loadUrl("javascript:replaceSuperCollapsedBlock(" + item.getStart() + ")");
}
+ private void showNewMessageNotification(NewMessagesInfo info) {
+ final TextView descriptionView = (TextView) mNewMessageBar.findViewById(
+ R.id.new_message_description);
+ descriptionView.setText(info.getNotificationText());
+ mNewMessageBar.setVisibility(View.VISIBLE);
+ }
+
+ private void onNewMessageBarClick() {
+ mNewMessageBar.setVisibility(View.GONE);
+
+ renderConversation(mPendingCursor);
+ mPendingCursor = null;
+ }
+
private static class MessageLoader extends CursorLoader {
private boolean mDeliveredFirstResults = false;
private final Conversation mConversation;
@@ -819,6 +856,16 @@
return ints;
}
+ @Override
+ public String toString() {
+ // log extra info at DEBUG level or finer
+ final String s = super.toString();
+ if (!LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG) || mConversation == null) {
+ return s;
+ }
+ return "(" + s + " subj=" + mConversation.subject + ")";
+ }
+
private class ConversationWebViewClient extends WebViewClient {
@Override
@@ -831,8 +878,8 @@
return;
}
- LogUtils.i(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s", url,
- ConversationViewFragment.this);
+ LogUtils.i(LOG_TAG, "IN CVF.onPageFinished, url=%s fragment=%s act=%s", url,
+ ConversationViewFragment.this, getActivity());
super.onPageFinished(view, url);
@@ -926,6 +973,32 @@
}
+ private class NewMessagesInfo {
+ int count;
+ String senderAddress;
+
+ /**
+ * Return the display text for the new message notification overlay. It will be formatted
+ * appropriately for a single new message vs. multiple new messages.
+ *
+ * @return display text
+ */
+ public String getNotificationText() {
+ final Object param;
+ if (count > 1) {
+ param = count;
+ } else {
+ Address addr = mAddressCache.get(senderAddress);
+ if (addr == null) {
+ addr = Address.getEmailAddress(senderAddress);
+ mAddressCache.put(senderAddress, addr);
+ }
+ param = TextUtils.isEmpty(addr.getName()) ? addr.getAddress() : addr.getName();
+ }
+ return getResources().getQuantityString(R.plurals.new_incoming_messages, count, param);
+ }
+ }
+
private class MessageLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
@Override
@@ -943,22 +1016,54 @@
return;
}
- // TODO: handle Gmail loading states (like LOADING and ERROR)
- if (messageCursor.getCount() == 0) {
- if (mCursor != null) {
- // TODO: need to exit this view- conversation may have been deleted, or for
- // whatever reason is now invalid
- } else {
- // ignore zero-sized cursors during initial load
- }
+ if (LogUtils.isLoggable(LOG_TAG, LogUtils.DEBUG)) {
+ LogUtils.d(LOG_TAG, "LOADED CONVERSATION= %s", messageCursor.getDebugDump());
+ }
+
+ // ignore cursors that are still loading results
+ if (!messageCursor.isLoaded()) {
return;
}
+ // TODO: handle ERROR status
+
+ if (messageCursor.getCount() == 0 && mCursor != null) {
+ // TODO: need to exit this view- conversation may have been deleted, or for
+ // whatever reason is now invalid (e.g. discard single draft)
+ return;
+ }
+
+ if (mCursor != null) {
+ final NewMessagesInfo info = getNewIncomingMessagesInfo(messageCursor);
+
+ if (info.count > 0) {
+ // don't immediately render new incoming messages from other senders
+ // (to avoid a new message from losing the user's focus)
+ //
+ // hold the new cursor as pending for later render
+ mPendingCursor = messageCursor;
+ LogUtils.i(LOG_TAG,
+ "conversation updated, holding cursor for new incoming message");
+
+ showNewMessageNotification(info);
+
+ return;
+ }
+ }
+
+ if (mCursor == null) {
+ LogUtils.i(LOG_TAG, "existing cursor is null, rendering from scratch");
+ } else {
+ // re-render?
+ // or render just those messages that changed?
+ LogUtils.i(LOG_TAG,
+ "conversation updated, but not due to incoming message. rendering.");
+ }
+ renderConversation(messageCursor);
+
// TODO: if this is not user-visible, delay render until user-visible fragment is done.
// This is needed in addition to the showConversation() delay to speed up rotation and
// restoration.
-
- renderConversation(messageCursor);
}
@Override
@@ -967,6 +1072,22 @@
// TODO: null out all Message.mMessageCursor references
}
+ private NewMessagesInfo getNewIncomingMessagesInfo(MessageCursor newCursor) {
+ final NewMessagesInfo info = new NewMessagesInfo();
+
+ int pos = -1;
+ while (newCursor.moveToPosition(++pos)) {
+ final Message m = newCursor.getMessage();
+ if (!mViewState.contains(m)) {
+ LogUtils.i(LOG_TAG, "conversation diff: found new msg: %s", m.uri);
+ // TODO: distinguish ours from theirs
+ info.count++;
+ info.senderAddress = m.from;
+ }
+ }
+ return info;
+ }
+
}
/**