Merge pull request #83 from andrewachen/messageview_save_location

Restore position in MessageView after rotation screen.
This commit is contained in:
Andrew Chen 2011-11-07 09:57:18 -08:00
commit ef631e21d1
6 changed files with 159 additions and 5 deletions

View file

@ -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");
}
}

View file

@ -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) {
}

View file

@ -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<MessagingListener> 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<MessagingListener> listeners) {
this.mListeners = listeners;
}
}

View file

@ -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<MessagingListener> 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<MessagingListener> listeners) {
this.mListeners = listeners;
}
}

View file

@ -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<MessagingListener> listeners) {
if(!mScreenReaderEnabled) {
if(mMessageContentView != null) {
mMessageContentView.setListeners(listeners);
}
} else {
if(mAccessibleMessageContentView != null) {
mAccessibleMessageContentView.setListeners(listeners);
}
}
}
}

View file

@ -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 <code>ScrollView</code>.
* @return
*/
public MessagingListener getListener() {
if(this.mListener != null) {
return this.mListener;
} else {
return this.mListener = new ScrollToLastLocationListener();
}
}
}