1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-13 13:05:03 -05:00
k-9/src/com/fsck/k9/activity/MessageView.java

566 lines
18 KiB
Java

package com.fsck.k9.activity;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.actionbarsherlock.app.ActionBar;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.actionbarsherlock.view.Window;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
import com.fsck.k9.Preferences;
import com.fsck.k9.R;
import com.fsck.k9.activity.misc.SwipeGestureDetector;
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
import com.fsck.k9.crypto.PgpData;
import com.fsck.k9.fragment.MessageViewFragment;
import com.fsck.k9.fragment.MessageViewFragment.MessageViewFragmentListener;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.view.MessageHeader;
import com.fsck.k9.view.MessageTitleView;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentTransaction;
import android.util.Log;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
public class MessageView extends K9FragmentActivity implements MessageViewFragmentListener,
OnSwipeGestureListener {
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_MESSAGE_LIST_EXTRAS = "com.fsck.k9.MessageView_messageListExtras";
/**
* @see #mLastDirection
*/
private static final int PREVIOUS = 1;
private static final int NEXT = 2;
public static void actionView(Context context, MessageReference messRef,
ArrayList<MessageReference> messReferences, Bundle messageListExtras) {
Intent i = new Intent(context, MessageView.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra(EXTRA_MESSAGE_LIST_EXTRAS, messageListExtras);
i.putExtra(EXTRA_MESSAGE_REFERENCE, messRef);
i.putParcelableArrayListExtra(EXTRA_MESSAGE_REFERENCES, messReferences);
context.startActivity(i);
}
private StorageManager.StorageListener mStorageListener = new StorageListenerImplementation();
private Account mAccount;
private MessageTitleView mTitleView;
private MessageReference mMessageReference;
private ArrayList<MessageReference> mMessageReferences;
private int mLastDirection = (K9.messageViewShowNext()) ? NEXT : PREVIOUS;
private MessageReference mNextMessage;
private MessageReference mPreviousMessage;
private MessageViewFragment mMessageViewFragment;
private Menu mMenu;
/**
* Used to detect right-to-left bezel swipes.
*
* @see #onSwipeRightToLeft(MotionEvent, MotionEvent)
*/
private int mRightBezelThreshold;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme(K9.getK9ThemeResourceId(K9.getK9MessageViewTheme()));
requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
setContentView(R.layout.message_view);
initializeActionBar();
setTitle("");
int screenWidth = getResources().getDisplayMetrics().widthPixels;
mRightBezelThreshold = screenWidth - SwipeGestureDetector.BEZEL_SWIPE_THRESHOLD;
// Enable gesture detection for MessageViews
if (K9.gesturesEnabled()) {
setupGestureDetector(this);
}
final Intent intent = getIntent();
Uri uri = intent.getData();
if (savedInstanceState != null) {
mMessageReference = savedInstanceState.getParcelable(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = savedInstanceState.getParcelableArrayList(EXTRA_MESSAGE_REFERENCES);
} else {
if (uri == null) {
mMessageReference = intent.getParcelableExtra(EXTRA_MESSAGE_REFERENCE);
mMessageReferences = intent.getParcelableArrayListExtra(EXTRA_MESSAGE_REFERENCES);
} else {
List<String> segmentList = uri.getPathSegments();
if (segmentList.size() != 3) {
//TODO: Use resource to externalize message
Toast.makeText(this, "Invalid intent uri: " + uri.toString(), Toast.LENGTH_LONG).show();
return;
}
String accountId = segmentList.get(0);
Collection<Account> accounts = Preferences.getPreferences(this).getAvailableAccounts();
for (Account account : accounts) {
if (String.valueOf(account.getAccountNumber()).equals(accountId)) {
mMessageReference = new MessageReference();
mMessageReference.accountUuid = account.getUuid();
mMessageReference.folderName = segmentList.get(1);
mMessageReference.uid = segmentList.get(2);
mMessageReferences = new ArrayList<MessageReference>();
mAccount = account;
break;
}
}
if (mMessageReference == null) {
//TODO: Use resource to externalize message
Toast.makeText(this, "Invalid account id: " + accountId, Toast.LENGTH_LONG).show();
return;
}
}
}
if (mAccount == null) {
Preferences preferences = Preferences.getPreferences(getApplicationContext());
mAccount = preferences.getAccount(mMessageReference.accountUuid);
}
findSurroundingMessagesUid();
FragmentManager fragmentManager = getSupportFragmentManager();
mMessageViewFragment = (MessageViewFragment) fragmentManager.findFragmentById(R.id.message);
if (mMessageViewFragment == null) {
FragmentTransaction ft = fragmentManager.beginTransaction();
mMessageViewFragment = MessageViewFragment.newInstance(mMessageReference);
ft.add(R.id.message, mMessageViewFragment);
ft.commit();
}
}
@Override
public void onResume() {
super.onResume();
if (!mAccount.isAvailable(this)) {
onAccountUnavailable();
return;
}
StorageManager.getInstance(getApplication()).addListener(mStorageListener);
}
@Override
protected void onPause() {
StorageManager.getInstance(getApplication()).removeListener(mStorageListener);
super.onPause();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelable(EXTRA_MESSAGE_REFERENCE, mMessageReference);
outState.putParcelableArrayList(EXTRA_MESSAGE_REFERENCES, mMessageReferences);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
getSupportMenuInflater().inflate(R.menu.message_view_option, menu);
mMenu = menu;
configureMenu(menu);
return true;
}
private void configureMenu(Menu menu) {
if (menu == null) {
return;
}
if (mNextMessage != null) {
menu.findItem(R.id.next_message).setEnabled(true);
menu.findItem(R.id.next_message).getIcon().setAlpha(255);
} else {
menu.findItem(R.id.next_message).getIcon().setAlpha(127);
menu.findItem(R.id.next_message).setEnabled(false);
}
if (mPreviousMessage != null) {
menu.findItem(R.id.previous_message).setEnabled(true);
menu.findItem(R.id.previous_message).getIcon().setAlpha(255);
} else {
menu.findItem(R.id.previous_message).getIcon().setAlpha(127);
menu.findItem(R.id.previous_message).setEnabled(false);
}
}
private void toggleActionsState(Menu menu, boolean state) {
for (int i = 0; i < menu.size(); ++i) {
menu.getItem(i).setEnabled(state);
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home: {
finish();
break;
}
case R.id.next_message: {
onNext();
break;
}
case R.id.previous_message: {
onPrevious();
break;
}
default: {
return super.onOptionsItemSelected(item);
}
}
return true;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
boolean ret = false;
if (KeyEvent.ACTION_DOWN == event.getAction()) {
ret = onCustomKeyDown(event.getKeyCode(), event);
}
if (!ret) {
ret = super.dispatchKeyEvent(event);
}
return ret;
}
/**
* Handle hotkeys
*
* <p>
* This method is called by {@link #dispatchKeyEvent(KeyEvent)} before any view had the chance
* to consume this key event.
* </p>
*
* @param keyCode
* The value in {@code event.getKeyCode()}.
* @param event
* Description of the key event.
*
* @return {@code true} if this event was consumed.
*/
public boolean onCustomKeyDown(final int keyCode, final KeyEvent event) {
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP: {
if (K9.useVolumeKeysForNavigationEnabled()) {
onNext();
return true;
}
break;
}
case KeyEvent.KEYCODE_VOLUME_DOWN: {
if (K9.useVolumeKeysForNavigationEnabled()) {
onPrevious();
return true;
}
break;
}
case KeyEvent.KEYCODE_DEL:
case KeyEvent.KEYCODE_D: {
mMessageViewFragment.onDelete();
return true;
}
case KeyEvent.KEYCODE_F: {
mMessageViewFragment.onForward();
return true;
}
case KeyEvent.KEYCODE_A: {
mMessageViewFragment.onReplyAll();
return true;
}
case KeyEvent.KEYCODE_R: {
mMessageViewFragment.onReply();
return true;
}
case KeyEvent.KEYCODE_G: {
mMessageViewFragment.onFlag();
return true;
}
case KeyEvent.KEYCODE_M: {
mMessageViewFragment.onMove();
return true;
}
case KeyEvent.KEYCODE_S: {
mMessageViewFragment.onRefile(mAccount.getSpamFolderName());
return true;
}
case KeyEvent.KEYCODE_V: {
mMessageViewFragment.onRefile(mAccount.getArchiveFolderName());
return true;
}
case KeyEvent.KEYCODE_Y: {
mMessageViewFragment.onCopy();
return true;
}
case KeyEvent.KEYCODE_J:
case KeyEvent.KEYCODE_P: {
onPrevious();
return true;
}
case KeyEvent.KEYCODE_N:
case KeyEvent.KEYCODE_K: {
onNext();
return true;
}
case KeyEvent.KEYCODE_Z: {
mMessageViewFragment.zoom(event);
return true;
}
case KeyEvent.KEYCODE_H: {
Toast toast = Toast.makeText(this, R.string.message_help_key, Toast.LENGTH_LONG);
toast.show();
return true;
}
}
return false;
}
@Override
public boolean onKeyUp(int keyCode, KeyEvent event) {
// Swallow these events too to avoid the audible notification of a volume change
if (K9.useVolumeKeysForNavigationEnabled()) {
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
if (K9.DEBUG) {
Log.v(K9.LOG_TAG, "Swallowed key up.");
}
return true;
}
}
return super.onKeyUp(keyCode, event);
}
protected void onAccountUnavailable() {
finish();
// TODO inform user about account unavailability using Toast
Accounts.listAccounts(this);
}
private void initializeActionBar() {
final ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowCustomEnabled(true);
actionBar.setCustomView(R.layout.actionbar_message_view);
final View customView = actionBar.getCustomView();
mTitleView = (MessageTitleView) customView.findViewById(android.R.id.title);
}
@Override
public void messageHeaderViewAvailable(MessageHeader header) {
mTitleView.setMessageHeader(header);
}
/**
* Set the title of the view.
*
* <p>Since we're using a custom ActionBar view, the normal {@code setTitle()} doesn't do what
* we think. This version sets the text value into the proper ActionBar title view.</p>
*
* @param title
* Title to set.
*/
@Override
public void setTitle(CharSequence title) {
mTitleView.setText(title);
}
@Override
public void setProgress(boolean enable) {
setSupportProgressBarIndeterminateVisibility(enable);
}
/**
* Handle a right-to-left swipe starting at the edge of the screen as "move to next message."
*/
@Override
public void onSwipeRightToLeft(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() > mRightBezelThreshold) {
onNext();
}
}
/**
* Handle a left-to-right swipe starting at the edge of the screen as
* "move to previous message."
*/
@Override
public void onSwipeLeftToRight(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() < SwipeGestureDetector.BEZEL_SWIPE_THRESHOLD) {
onPrevious();
}
}
@Override
public void showNextMessageOrReturn() {
if (K9.messageViewReturnToList()) {
finish();
} else {
showNextMessage();
}
}
private void showNextMessage() {
findSurroundingMessagesUid();
mMessageReferences.remove(mMessageReference);
if (mLastDirection == NEXT && mNextMessage != null) {
onNext();
} else if (mLastDirection == PREVIOUS && mPreviousMessage != null) {
onPrevious();
} else if (mNextMessage != null) {
onNext();
} else if (mPreviousMessage != null) {
onPrevious();
} else {
finish();
}
}
protected void onNext() {
// Reset scroll percentage when we change messages
if (mNextMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
}
mLastDirection = NEXT;
toggleActionsState(mMenu, false);
// if (K9.showAnimations()) {
// mMessageView.startAnimation(outToLeftAnimation());
// }
displayMessage(mNextMessage);
}
protected void onPrevious() {
// Reset scroll percentage when we change messages
if (mPreviousMessage == null) {
Toast.makeText(this, getString(R.string.end_of_folder), Toast.LENGTH_SHORT).show();
return;
}
mLastDirection = PREVIOUS;
toggleActionsState(mMenu, false);
// if (K9.showAnimations()) {
// mMessageView.startAnimation(inFromRightAnimation());
// }
displayMessage(mPreviousMessage);
}
private void displayMessage(MessageReference reference) {
mMessageReference = reference;
findSurroundingMessagesUid();
configureMenu(mMenu);
mMessageViewFragment.displayMessage(reference);
}
private void findSurroundingMessagesUid() {
mNextMessage = mPreviousMessage = null;
int i = mMessageReferences.indexOf(mMessageReference);
if (i < 0) {
return;
}
if (i != 0) {
mNextMessage = mMessageReferences.get(i - 1);
}
if (i != (mMessageReferences.size() - 1)) {
mPreviousMessage = mMessageReferences.get(i + 1);
}
}
private final class StorageListenerImplementation implements StorageManager.StorageListener {
@Override
public void onUnmount(String providerId) {
if (!providerId.equals(mAccount.getLocalStorageProviderId())) {
return;
}
runOnUiThread(new Runnable() {
@Override
public void run() {
onAccountUnavailable();
}
});
}
@Override
public void onMount(String providerId) { /* no-op */ }
}
@Override
public void restartActivity() {
// restart the current activity, so that the theme change can be applied
if (Build.VERSION.SDK_INT < 11) {
Intent intent = getIntent();
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
finish();
overridePendingTransition(0, 0); // disable animations to speed up the switch
startActivity(intent);
overridePendingTransition(0, 0);
} else {
recreate();
}
}
@Override
public void displayMessageSubject(String subject) {
setTitle(subject);
}
@Override
public void onReply(Message message, PgpData pgpData) {
MessageCompose.actionReply(this, mAccount, message, false, pgpData.getDecryptedData());
finish();
}
@Override
public void onReplyAll(Message message, PgpData pgpData) {
MessageCompose.actionReply(this, mAccount, message, true, pgpData.getDecryptedData());
finish();
}
@Override
public void onForward(Message mMessage, PgpData mPgpData) {
MessageCompose.actionForward(this, mAccount, mMessage, mPgpData.getDecryptedData());
finish();
}
}