diff --git a/src/com/fsck/k9/activity/MessageView.java b/src/com/fsck/k9/activity/MessageView.java index 888e57d0b..f969b9d55 100644 --- a/src/com/fsck/k9/activity/MessageView.java +++ b/src/com/fsck/k9/activity/MessageView.java @@ -35,6 +35,7 @@ public class MessageView extends K9Activity implements OnClickListener { private static final String EXTRA_MESSAGE_REFERENCE = "com.fsck.k9.MessageView_messageReference"; private static final String EXTRA_MESSAGE_REFERENCES = "com.fsck.k9.MessageView_messageReferences"; private static final String EXTRA_NEXT = "com.fsck.k9.MessageView_next"; + private static final String EXTRA_SCROLL_PERCENTAGE = "com.fsck.k9.MessageView_scrollPercentage"; private static final String SHOW_PICTURES = "showPictures"; private static final String STATE_PGP_DATA = "pgpData"; private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1; @@ -372,13 +373,19 @@ public class MessageView extends K9Activity implements OnClickListener { } }; }); + mMessageView.initialize(this); + // Register the ScrollView's listener to handle scrolling to last known location on resume. + mController.addListener(mTopView.getListener()); + mMessageView.setListeners(mController.getListeners()); + setTitle(""); final Intent intent = getIntent(); Uri uri = intent.getData(); if (icicle != null) { + // TODO This code seems unnecessary since the icicle should already be thawed in onRestoreInstanceState(). mMessageReference = icicle.getParcelable(EXTRA_MESSAGE_REFERENCE); mMessageReferences = icicle.getParcelableArrayList(EXTRA_MESSAGE_REFERENCES); mPgpData = (PgpData) icicle.getSerializable(STATE_PGP_DATA); @@ -492,6 +499,7 @@ public class MessageView extends K9Activity implements OnClickListener { outState.putParcelableArrayList(EXTRA_MESSAGE_REFERENCES, mMessageReferences); outState.putSerializable(STATE_PGP_DATA, mPgpData); outState.putBoolean(SHOW_PICTURES, mMessageView.showPictures()); + outState.putDouble(EXTRA_SCROLL_PERCENTAGE, mTopView.getScrollPercentage()); } @Override @@ -500,6 +508,7 @@ public class MessageView extends K9Activity implements OnClickListener { mPgpData = (PgpData) savedInstanceState.getSerializable(STATE_PGP_DATA); mMessageView.updateCryptoLayout(mAccount.getCryptoProvider(), mPgpData, mMessage); mMessageView.setLoadPictures(savedInstanceState.getBoolean(SHOW_PICTURES)); + mTopView.setScrollPercentage(savedInstanceState.getDouble(EXTRA_SCROLL_PERCENTAGE)); } private void displayMessage(MessageReference ref) { @@ -619,11 +628,13 @@ public class MessageView extends K9Activity implements OnClickListener { onAccountUnavailable(); return; } + mController.addListener(mTopView.getListener()); StorageManager.getInstance(getApplication()).addListener(mStorageListener); } @Override protected void onPause() { + mController.removeListener(mTopView.getListener()); StorageManager.getInstance(getApplication()).removeListener(mStorageListener); super.onPause(); } @@ -850,6 +861,8 @@ public class MessageView extends K9Activity implements OnClickListener { } protected void onNext() { + // Reset scroll percentage when we change messages + mTopView.setScrollPercentage(0); if (mNextMessage == null) { Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show(); return; @@ -864,6 +877,8 @@ public class MessageView extends K9Activity implements OnClickListener { } protected void onPrevious() { + // Reset scroll percentage when we change messages + mTopView.setScrollPercentage(0); if (mPreviousMessage == null) { Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show(); return; @@ -1091,8 +1106,8 @@ public class MessageView extends K9Activity implements OnClickListener { mTopView.scrollTo(0, 0); try { if (MessageView.this.mMessage != null - && MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL) - && message.isSet(Flag.X_DOWNLOADED_FULL)) { + && MessageView.this.mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL) + && message.isSet(Flag.X_DOWNLOADED_FULL)) { mMessageView.setHeaders(message, account); } MessageView.this.mMessage = message; @@ -1252,5 +1267,4 @@ public class MessageView extends K9Activity implements OnClickListener { // sometimes shows the original encrypted content mMessageView.loadBodyFromText(mAccount.getCryptoProvider(), mPgpData, mMessage, mPgpData.getDecryptedData(), "text/plain"); } - } diff --git a/src/com/fsck/k9/controller/MessagingListener.java b/src/com/fsck/k9/controller/MessagingListener.java index bed951a2a..bdf35fa00 100644 --- a/src/com/fsck/k9/controller/MessagingListener.java +++ b/src/com/fsck/k9/controller/MessagingListener.java @@ -112,6 +112,11 @@ public class MessagingListener { public void loadMessageForViewFailed(Account account, String folder, String uid, Throwable t) { } + /** + * Called when a message for view has been fully displayed on the screen. + */ + public void messageViewFinished() {} + public void checkMailStarted(Context context, Account account) { } diff --git a/src/com/fsck/k9/view/AccessibleWebView.java b/src/com/fsck/k9/view/AccessibleWebView.java index 7112e6dab..5dd8bb9a8 100644 --- a/src/com/fsck/k9/view/AccessibleWebView.java +++ b/src/com/fsck/k9/view/AccessibleWebView.java @@ -26,11 +26,15 @@ import android.webkit.WebView; import android.widget.TextView; import com.fsck.k9.activity.AccessibleEmailContentActivity; +import com.fsck.k9.controller.MessagingListener; + +import java.util.Set; public class AccessibleWebView extends TextView { private Context mContext; private String mHtmlSource; private WebView mDummyWebView; + private Set mListeners = null; public AccessibleWebView(Context context) { super(context); @@ -68,6 +72,13 @@ public class AccessibleWebView extends TextView { String historyUrl) { mHtmlSource = data; this.setText(Html.fromHtml(mHtmlSource, null, null)); + + // Let everyone know that loading has finished. + if (mListeners != null) { + for (MessagingListener l : mListeners) { + l.messageViewFinished(); + } + } } public boolean zoomIn() { @@ -92,4 +103,8 @@ public class AccessibleWebView extends TextView { i.putExtra("content", mHtmlSource); mContext.startActivity(i); } + + public void setListeners(final Set listeners) { + this.mListeners = listeners; + } } diff --git a/src/com/fsck/k9/view/MessageWebView.java b/src/com/fsck/k9/view/MessageWebView.java index 9871e503e..4d22c07d6 100644 --- a/src/com/fsck/k9/view/MessageWebView.java +++ b/src/com/fsck/k9/view/MessageWebView.java @@ -1,6 +1,7 @@ package com.fsck.k9.view; import android.content.Context; +import android.graphics.Picture; import android.os.Build; import android.util.AttributeSet; import android.util.Log; @@ -10,10 +11,15 @@ import android.webkit.WebView; import android.widget.Toast; import com.fsck.k9.K9; import com.fsck.k9.R; +import com.fsck.k9.controller.MessagingListener; import java.lang.reflect.Method; +import java.util.Set; public class MessageWebView extends WebView { + // Store a reference to the listeners in MessagingController. We can't fetch it directly since + // we don't know the application name. + private Set mListeners = null; /** * We use WebSettings.getBlockNetworkLoads() to prevent the WebView that displays email @@ -97,6 +103,18 @@ public class MessageWebView extends WebView { // Disable network images by default. This is overridden by preferences. blockNetworkData(true); + + // Listen for when the screen has finished drawing. + setPictureListener(new PictureListener() { + @Override + public void onNewPicture(WebView webView, Picture picture) { + if (mListeners != null) { + for (MessagingListener l : mListeners) { + l.messageViewFinished(); + } + } + } + }); } /* @@ -115,4 +133,8 @@ public class MessageWebView extends WebView { Log.e(K9.LOG_TAG, "Exception in emulateShiftHeld()", e); } } + + public void setListeners(final Set listeners) { + this.mListeners = listeners; + } } diff --git a/src/com/fsck/k9/view/SingleMessageView.java b/src/com/fsck/k9/view/SingleMessageView.java index 85afd51f3..48206f8ed 100644 --- a/src/com/fsck/k9/view/SingleMessageView.java +++ b/src/com/fsck/k9/view/SingleMessageView.java @@ -29,6 +29,7 @@ import com.fsck.k9.mail.internet.MimeUtility; import com.fsck.k9.mail.store.LocalStore; import java.util.List; +import java.util.Set; /** @@ -116,11 +117,10 @@ public class SingleMessageView extends LinearLayout { return false; } - - public boolean showPictures() { return mShowPictures; } + public void setShowPictures(Boolean show) { mShowPictures = show; } @@ -312,4 +312,21 @@ public class SingleMessageView extends LinearLayout { AttachmentView.AttachmentFileDownloadCallback attachmentCallback) { this.attachmentCallback = attachmentCallback; } + + /** + * Save a copy of the {@link com.fsck.k9.controller.MessagingController#getListeners()}. This method will also + * pass along these listeners to the underlying views. + * @param listeners Set of listeners. + */ + public void setListeners(final Set listeners) { + if(!mScreenReaderEnabled) { + if(mMessageContentView != null) { + mMessageContentView.setListeners(listeners); + } + } else { + if(mAccessibleMessageContentView != null) { + mAccessibleMessageContentView.setListeners(listeners); + } + } + } } diff --git a/src/com/fsck/k9/view/ToggleScrollView.java b/src/com/fsck/k9/view/ToggleScrollView.java index 16ee41ab1..714e8ce24 100644 --- a/src/com/fsck/k9/view/ToggleScrollView.java +++ b/src/com/fsck/k9/view/ToggleScrollView.java @@ -2,13 +2,22 @@ package com.fsck.k9.view; import android.content.Context; import android.util.AttributeSet; +import android.util.Log; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.ScrollView; +import com.fsck.k9.K9; +import com.fsck.k9.controller.MessagingListener; +/** + * An extension of {@link ScrollView} that allows scrolling to be selectively disabled. + */ public class ToggleScrollView extends ScrollView { private GestureDetector mDetector; private boolean mScrolling = true; + private int mCurrentYPosition; + private double mScrollPercentage; + private ScrollToLastLocationListener mListener; public ToggleScrollView(Context context, AttributeSet attrs) { super(context, attrs); @@ -59,4 +68,76 @@ public class ToggleScrollView extends ScrollView { return false; } } + + /** + * Fetch the current percentage by which this view has been scrolled. + * @return Scroll percentage based on the top edge of the screen, from 0 to 100. This number should never really + * be 100, unless the screen is of 0 height... + */ + public double getScrollPercentage() { + // We save only the Y coordinate instead of the percentage because I don't know how expensive the + // computeVerticalScrollRange() call is. + final int scrollRange = computeVerticalScrollRange(); + if(scrollRange == 0) { + return 0; + } + return (double) mCurrentYPosition / scrollRange; + } + + /** + * Set the percentage by which we should scroll the page once we get the load complete event. This is + * based on the top edge of the view. + * @param percentage Percentage of page to scroll to. + */ + public void setScrollPercentage(final double percentage) { + Log.d(K9.LOG_TAG, "ToggleView: Setting last scroll percentage to " + percentage); + this.mScrollPercentage = percentage; + } + + /** + * Override {@link ScrollView#onScrollChanged(int, int, int, int)} to record the current x/y position. We use this + * to save our current position for future scrolling. + * + * @param x + * @param y + * @param oldx + * @param oldy + */ + @Override + protected void onScrollChanged(int x, int y, int oldx, int oldy) { + super.onScrollChanged(x, y, oldx, oldy); + + this.mCurrentYPosition = y; + // I wish Android has a TRACE log level so I wouldn't have to comment this out. This one is really noisy. + // Log.d(K9.LOG_TAG, "ToggleScrollView: mCurrentYPosition=" + y + " scrollRange=" + computeVerticalScrollRange() + " pct=" + getScrollPercentage()); + } + + /** + * This is a {@link MessagingListener} which listens for when the a message has finished being displayed on the + * screen. We'll scroll the message to the user's last known location once it's done. + */ + class ScrollToLastLocationListener extends MessagingListener { + public void messageViewFinished() { + // Don't scroll if our last position was at the top. + if(mScrollPercentage != 0.0) { + final int scrollRange = computeVerticalScrollRange(); + final int newY = (int)(mScrollPercentage * scrollRange); + Log.d(K9.LOG_TAG, "ToggleScrollView: requested " + (100 * mScrollPercentage) + "%, " + + "scrolling to " + newY + "/" + scrollRange); + scrollTo(0, newY); + } + } + } + + /** + * Fetch the {@link MessagingListener} for this ScrollView. + * @return + */ + public MessagingListener getListener() { + if(this.mListener != null) { + return this.mListener; + } else { + return this.mListener = new ScrollToLastLocationListener(); + } + } }