1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-12-24 16:48:50 -05:00

Converted message view to a fragment

The fragment should be fully functional. The only thing missing is the
animation when showing the next/previous message.
This commit is contained in:
cketti 2012-09-26 18:03:14 +02:00
parent c6bea2e6ab
commit bbcc4988ba
14 changed files with 1386 additions and 653 deletions

View File

@ -1,10 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@id/message"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<include layout="@layout/message"/>
</LinearLayout>

View File

@ -0,0 +1,68 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/delete"
android:alphabeticShortcut="q"
android:title="@string/delete_action"
android:icon="?attr/iconActionDelete"
android:showAsAction="always"
/>
<item
android:id="@+id/single_message_options"
android:icon="?attr/iconActionSingleMessageOptions"
android:showAsAction="ifRoom"
android:title="@string/single_message_options_action">
<menu>
<item
android:id="@+id/reply"
android:title="@string/reply_action"/>
<item
android:id="@+id/reply_all"
android:title="@string/reply_all_action"/>
<item
android:id="@+id/forward"
android:title="@string/forward_action"/>
<item
android:id="@+id/share"
android:title="@string/send_alternate_action" />
</menu>
</item>
<item
android:id="@+id/archive"
android:title="@string/archive_action"
android:icon="?attr/iconActionArchive"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/spam"
android:title="@string/spam_action"
android:icon="?attr/iconActionSpam"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/move"
android:showAsAction="ifRoom"
android:icon="?attr/iconActionMoveOrCopy"
android:title="@string/move_action"
/>
<item
android:id="@+id/copy"
android:title="@string/copy_action"
/>
<item
android:id="@+id/toggle_unread"
android:alphabeticShortcut="u"
android:title="@string/mark_as_unread_action"
android:showAsAction="never"
/>
<item
android:id="@+id/select_text"
android:title="@string/select_text_action"
android:showAsAction="never"
/>
<item
android:id="@+id/toggle_message_view_theme"
android:title="@string/message_view_theme_action_dark"
android:showAsAction="never"
/>
</menu>

View File

@ -12,69 +12,4 @@
android:icon="?attr/iconActionPreviousMessage"
android:showAsAction="always"
/>
<item
android:id="@+id/delete"
android:alphabeticShortcut="q"
android:title="@string/delete_action"
android:icon="?attr/iconActionDelete"
android:showAsAction="always"
/>
<item
android:id="@+id/single_message_options"
android:icon="?attr/iconActionSingleMessageOptions"
android:showAsAction="ifRoom"
android:title="@string/single_message_options_action">
<menu>
<item
android:id="@+id/reply"
android:title="@string/reply_action"/>
<item
android:id="@+id/reply_all"
android:title="@string/reply_all_action"/>
<item
android:id="@+id/forward"
android:title="@string/forward_action"/>
<item
android:id="@+id/share"
android:title="@string/send_alternate_action" />
</menu>
</item>
<item
android:id="@+id/archive"
android:title="@string/archive_action"
android:icon="?attr/iconActionArchive"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/spam"
android:title="@string/spam_action"
android:icon="?attr/iconActionSpam"
android:showAsAction="ifRoom"
/>
<item
android:id="@+id/move"
android:showAsAction="ifRoom"
android:icon="?attr/iconActionMoveOrCopy"
android:title="@string/move_action"
/>
<item
android:id="@+id/copy"
android:title="@string/copy_action"
/>
<item
android:id="@+id/toggle_unread"
android:alphabeticShortcut="u"
android:title="@string/mark_as_unread_action"
android:showAsAction="never"
/>
<item
android:id="@+id/select_text"
android:title="@string/select_text_action"
android:showAsAction="never"
/>
<item
android:id="@+id/toggle_message_view_theme"
android:title="@string/message_view_theme_action_dark"
android:showAsAction="never"
/>
</menu>

View File

@ -0,0 +1,230 @@
package com.fsck.k9.activity;
import java.util.Locale;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.MotionEvent;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Animation;
import android.view.animation.TranslateAnimation;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.fsck.k9.K9;
public class K9FragmentActivity extends SherlockFragmentActivity {
protected static final int BEZEL_SWIPE_THRESHOLD = 20;
protected GestureDetector mGestureDetector;
@Override
public void onCreate(Bundle icicle) {
setLanguage(this, K9.getK9Language());
setTheme(K9.getK9ThemeResourceId());
super.onCreate(icicle);
setupFormats();
}
public static void setLanguage(Context context, String language) {
Locale locale;
if (language == null || language.equals("")) {
locale = Locale.getDefault();
} else if (language.length() == 5 && language.charAt(2) == '_') {
// language is in the form: en_US
locale = new Locale(language.substring(0, 2), language.substring(3));
} else {
locale = new Locale(language);
}
Configuration config = new Configuration();
config.locale = locale;
context.getResources().updateConfiguration(config,
context.getResources().getDisplayMetrics());
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mGestureDetector != null) {
mGestureDetector.onTouchEvent(ev);
}
return super.dispatchTouchEvent(ev);
}
@Override
public void onResume() {
super.onResume();
setupFormats();
}
private java.text.DateFormat mTimeFormat;
private void setupFormats() {
mTimeFormat = android.text.format.DateFormat.getTimeFormat(this); // 12/24 date format
}
public java.text.DateFormat getTimeFormat() {
return mTimeFormat;
}
/**
* Called when a swipe from right to left is handled by {@link MyGestureDetector}. See
* {@link android.view.GestureDetector.OnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float)}
* for more information on the {@link MotionEvent}s being passed.
* @param e1 First down motion event that started the fling.
* @param e2 The move motion event that triggered the current onFling.
*/
protected void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2) {
}
/**
* Called when a swipe from left to right is handled by {@link MyGestureDetector}. See
* {@link android.view.GestureDetector.OnGestureListener#onFling(android.view.MotionEvent, android.view.MotionEvent, float, float)}
* for more information on the {@link MotionEvent}s being passed.
* @param e1 First down motion event that started the fling.
* @param e2 The move motion event that triggered the current onFling.
*/
protected void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2) {
}
protected Animation inFromRightAnimation() {
return slideAnimation(0.0f, +1.0f);
}
protected Animation outToLeftAnimation() {
return slideAnimation(0.0f, -1.0f);
}
private Animation slideAnimation(float right, float left) {
Animation slide = new TranslateAnimation(
Animation.RELATIVE_TO_PARENT, right, Animation.RELATIVE_TO_PARENT, left,
Animation.RELATIVE_TO_PARENT, 0.0f, Animation.RELATIVE_TO_PARENT, 0.0f
);
slide.setDuration(125);
slide.setFillBefore(true);
slide.setInterpolator(new AccelerateInterpolator());
return slide;
}
class MyGestureDetector extends SimpleOnGestureListener {
private boolean gesturesEnabled = false;
/**
* Creates a new {@link android.view.GestureDetector.OnGestureListener}. Enabled/disabled based upon
* {@link com.fsck.k9.K9#gesturesEnabled()}}.
*/
public MyGestureDetector() {
super();
}
/**
* Create a new {@link android.view.GestureDetector.OnGestureListener}.
* @param gesturesEnabled Setting to <code>true</code> will enable gesture detection,
* regardless of the system-wide gesture setting.
*/
public MyGestureDetector(final boolean gesturesEnabled) {
super();
this.gesturesEnabled = gesturesEnabled;
}
private static final float SWIPE_MAX_OFF_PATH_DIP = 250f;
private static final float SWIPE_THRESHOLD_VELOCITY_DIP = 325f;
protected MotionEvent mLastOnDownEvent = null;
@Override
public boolean onDown(MotionEvent e) {
mLastOnDownEvent = e;
return super.onDown(e);
}
@Override
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
// Do fling-detection if gestures are force-enabled or we have system-wide gestures enabled.
if (gesturesEnabled || K9.gesturesEnabled()) {
// Apparently sometimes e1 is null
// Found a workaround here: http://stackoverflow.com/questions/4151385/
if (e1 == null) {
e1 = mLastOnDownEvent;
}
// Make sure we avoid NullPointerExceptions
if (e1 == null || e2 == null) {
return false;
}
// Calculate the minimum distance required for this to count as a swipe.
// Convert the constant dips to pixels.
final float mGestureScale = getResources().getDisplayMetrics().density;
final int minVelocity = (int)(SWIPE_THRESHOLD_VELOCITY_DIP * mGestureScale + 0.5f);
final int maxOffPath = (int)(SWIPE_MAX_OFF_PATH_DIP * mGestureScale + 0.5f);
// Calculate how much was actually swiped.
final float deltaX = e2.getX() - e1.getX();
final float deltaY = e2.getY() - e1.getY();
// Calculate the minimum distance required for this to be considered a swipe.
final int minDistance = (int)Math.abs(deltaY * 4);
if(K9.DEBUG) {
final boolean movedAcross = (Math.abs(deltaX) > Math.abs(deltaY * 4));
final boolean steadyHand = (Math.abs(deltaX / deltaY) > 2);
Log.d(K9.LOG_TAG, String.format("Old swipe algorithm: movedAcross=%s steadyHand=%s result=%s", movedAcross, steadyHand, movedAcross && steadyHand));
Log.d(K9.LOG_TAG, String.format("New swipe algorithm: deltaX=%.2f deltaY=%.2f minDistance=%d velocity=%.2f (min=%d)", deltaX, deltaY, minDistance, velocityX, minVelocity));
}
try {
if (Math.abs(deltaY) > maxOffPath) {
if(K9.DEBUG)
Log.d(K9.LOG_TAG, "New swipe algorithm: Swipe too far off horizontal path.");
return false;
}
if(Math.abs(velocityX) < minVelocity) {
if(K9.DEBUG)
Log.d(K9.LOG_TAG, "New swipe algorithm: Swipe too slow.");
return false;
}
// right to left swipe
if (deltaX < (minDistance * -1)) {
onSwipeRightToLeft(e1, e2);
if(K9.DEBUG)
Log.d(K9.LOG_TAG, "New swipe algorithm: Right to Left swipe OK.");
} else if (deltaX > minDistance) {
onSwipeLeftToRight(e1, e2);
if(K9.DEBUG)
Log.d(K9.LOG_TAG, "New swipe algorithm: Left to Right swipe OK.");
} else {
if(K9.DEBUG)
Log.d(K9.LOG_TAG, "New swipe algorithm: Swipe did not meet minimum distance requirements.");
return false;
}
// successful fling, cancel the 2nd event to prevent any other action from happening
// see http://code.google.com/p/android/issues/detail?id=8497
e2.setAction(MotionEvent.ACTION_CANCEL);
} catch (Exception e) {
// nothing
}
}
return false;
}
}
public int getThemeBackgroundColor() {
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
android.R.attr.colorBackground,
});
int backgroundColor = array.getColor(0, 0xFF00FF);
array.recycle();
return backgroundColor;
}
}

View File

@ -0,0 +1,564 @@
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.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.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Toast;
public class MessageView extends K9FragmentActivity implements MessageViewFragmentListener {
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;
/**
* Screen width in pixels.
*
* <p>
* Used to detect right-to-left bezel swipes.
* </p>
*
* @see #onSwipeRightToLeft(MotionEvent, MotionEvent)
*/
private int mScreenWidthInPixels;
@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("");
mScreenWidthInPixels = getResources().getDisplayMetrics().widthPixels;
// Enable gesture detection for MessageViews
mGestureDetector = new GestureDetector(new MyGestureDetector(false));
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
protected void onSwipeRightToLeft(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() > mScreenWidthInPixels - BEZEL_SWIPE_THRESHOLD) {
onNext();
}
}
/**
* Handle a left-to-right swipe starting at the edge of the screen as
* "move to previous message."
*/
@Override
protected void onSwipeLeftToRight(MotionEvent e1, MotionEvent e2) {
if ((int) e1.getRawX() < 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();
}
}

View File

@ -13,11 +13,11 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.database.Cursor;
import android.net.Uri;
import android.support.v4.app.Fragment;
import android.widget.Toast;
import com.fsck.k9.R;
import com.fsck.k9.activity.MessageCompose;
import com.fsck.k9.activity.MessageView;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Part;
@ -74,10 +74,11 @@ public class Apg extends CryptoProvider {
public static final String INTENT_VERSION = "1";
public static final int DECRYPT_MESSAGE = 0x21070001;
public static final int ENCRYPT_MESSAGE = 0x21070002;
public static final int SELECT_PUBLIC_KEYS = 0x21070003;
public static final int SELECT_SECRET_KEY = 0x21070004;
// Note: The support package only allows us to use the lower 16 bits of a request code.
public static final int DECRYPT_MESSAGE = 0x0000A001;
public static final int ENCRYPT_MESSAGE = 0x0000A002;
public static final int SELECT_PUBLIC_KEYS = 0x0000A003;
public static final int SELECT_SECRET_KEY = 0x0000A004;
public static Pattern PGP_MESSAGE =
Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
@ -406,7 +407,19 @@ public class Apg extends CryptoProvider {
}
break;
case Apg.DECRYPT_MESSAGE:
default:
return false;
}
return true;
}
@Override
public boolean onDecryptActivityResult(CryptoDecryptCallback callback, int requestCode,
int resultCode, android.content.Intent data, PgpData pgpData) {
switch (requestCode) {
case Apg.DECRYPT_MESSAGE: {
if (resultCode != Activity.RESULT_OK || data == null) {
break;
}
@ -417,13 +430,14 @@ public class Apg extends CryptoProvider {
pgpData.setSignatureUnknown(data.getBooleanExtra(Apg.EXTRA_SIGNATURE_UNKNOWN, false));
pgpData.setDecryptedData(data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE));
((MessageView) activity).onDecryptDone(pgpData);
callback.onDecryptDone(pgpData);
break;
default:
}
default: {
return false;
}
}
return true;
}
@ -458,13 +472,13 @@ public class Apg extends CryptoProvider {
/**
* Start the decrypt activity.
*
* @param activity
* @param fragment
* @param data
* @param pgpData
* @return success or failure
*/
@Override
public boolean decrypt(Activity activity, String data, PgpData pgpData) {
public boolean decrypt(Fragment fragment, String data, PgpData pgpData) {
android.content.Intent intent = new android.content.Intent(Apg.Intent.DECRYPT_AND_RETURN);
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
intent.setType("text/plain");
@ -473,10 +487,10 @@ public class Apg extends CryptoProvider {
}
try {
intent.putExtra(EXTRA_TEXT, data);
activity.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
fragment.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
return true;
} catch (ActivityNotFoundException e) {
Toast.makeText(activity, R.string.error_activity_not_found, Toast.LENGTH_SHORT).show();
Toast.makeText(fragment.getActivity(), R.string.error_activity_not_found, Toast.LENGTH_SHORT).show();
return false;
}
}

View File

@ -3,6 +3,7 @@ package com.fsck.k9.crypto;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import com.fsck.k9.mail.Message;
@ -19,10 +20,12 @@ abstract public class CryptoProvider {
abstract public boolean isSigned(Message message);
abstract public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
Intent data, PgpData pgpData);
abstract public boolean onDecryptActivityResult(CryptoDecryptCallback callback,
int requestCode, int resultCode, Intent data, PgpData pgpData);
abstract public boolean selectSecretKey(Activity activity, PgpData pgpData);
abstract public boolean selectEncryptionKeys(Activity activity, String emails, PgpData pgpData);
abstract public boolean encrypt(Activity activity, String data, PgpData pgpData);
abstract public boolean decrypt(Activity activity, String data, PgpData pgpData);
abstract public boolean decrypt(Fragment fragment, String data, PgpData pgpData);
abstract public long[] getSecretKeyIdsFromEmail(Context context, String email);
abstract public long[] getPublicKeyIdsFromEmail(Context context, String email);
abstract public boolean hasSecretKeyForEmail(Context context, String email);
@ -38,4 +41,8 @@ abstract public class CryptoProvider {
return None.createInstance();
}
public interface CryptoDecryptCallback {
void onDecryptDone(PgpData pgpData);
}
}

View File

@ -2,6 +2,8 @@ package com.fsck.k9.crypto;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.v4.app.Fragment;
import com.fsck.k9.mail.Message;
@ -63,13 +65,19 @@ public class None extends CryptoProvider {
return false;
}
@Override
public boolean onDecryptActivityResult(CryptoDecryptCallback callback, int requestCode,
int resultCode, Intent data, PgpData pgpData) {
return false;
}
@Override
public boolean encrypt(Activity activity, String data, PgpData pgpData) {
return false;
}
@Override
public boolean decrypt(Activity activity, String data, PgpData pgpData) {
public boolean decrypt(Fragment fragment, String data, PgpData pgpData) {
return false;
}

View File

@ -0,0 +1,95 @@
package com.fsck.k9.fragment;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockDialogFragment;
public class ConfirmationDialogFragment extends SherlockDialogFragment implements OnClickListener,
OnCancelListener {
private static final String ARG_DIALOG_ID = "dialog_id";
private static final String ARG_TITLE = "title";
private static final String ARG_MESSAGE = "message";
private static final String ARG_CONFIRM_TEXT = "confirm";
private static final String ARG_CANCEL_TEXT = "cancel";
public static ConfirmationDialogFragment newInstance(int dialogId, String title, String message,
String confirmText, String cancelText) {
ConfirmationDialogFragment fragment = new ConfirmationDialogFragment();
Bundle args = new Bundle();
args.putInt(ARG_DIALOG_ID, dialogId);
args.putString(ARG_TITLE, title);
args.putString(ARG_MESSAGE, message);
args.putString(ARG_CONFIRM_TEXT, confirmText);
args.putString(ARG_CANCEL_TEXT, cancelText);
fragment.setArguments(args);
return fragment;
}
public interface ConfirmationDialogFragmentListener {
void doPositiveClick(int dialogId);
void doNegativeClick(int dialogId);
void dialogCancelled(int dialogId);
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
String title = args.getString(ARG_TITLE);
String message = args.getString(ARG_MESSAGE);
String confirmText = args.getString(ARG_CONFIRM_TEXT);
String cancelText = args.getString(ARG_CANCEL_TEXT);
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
builder.setTitle(title);
builder.setMessage(message);
builder.setPositiveButton(confirmText, this);
builder.setNegativeButton(cancelText, this);
return builder.create();
}
@Override
public void onClick(DialogInterface dialog, int which) {
switch (which) {
case DialogInterface.BUTTON_POSITIVE: {
getListener().doPositiveClick(getDialogId());
break;
}
case DialogInterface.BUTTON_NEGATIVE: {
getListener().doNegativeClick(getDialogId());
break;
}
}
}
@Override
public void onCancel(DialogInterface dialog) {
super.onCancel(dialog);
getListener().dialogCancelled(getDialogId());
}
private int getDialogId() {
return getArguments().getInt(ARG_DIALOG_ID);
}
private ConfirmationDialogFragmentListener getListener() {
try {
return (ConfirmationDialogFragmentListener) getTargetFragment();
} catch (ClassCastException e) {
throw new ClassCastException(getTargetFragment().getClass() +
" must implement ConfirmationDialogFragmentListener");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,35 @@
package com.fsck.k9.fragment;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.os.Bundle;
import com.actionbarsherlock.app.SherlockDialogFragment;
public class ProgressDialogFragment extends SherlockDialogFragment {
private static final String ARG_TITLE = "title";
public static ProgressDialogFragment newInstance(String title) {
ProgressDialogFragment fragment = new ProgressDialogFragment();
Bundle args = new Bundle();
args.putString(ARG_TITLE, title);
fragment.setArguments(args);
return fragment;
}
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
Bundle args = getArguments();
String title = args.getString(ARG_TITLE);
ProgressDialog dialog = new ProgressDialog(getActivity());
dialog.setIndeterminate(true);
dialog.setTitle(title);
return dialog;
}
}

View File

@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.support.v4.app.Fragment;
import android.text.InputType;
import android.widget.EditText;
@ -104,6 +105,38 @@ public class FileBrowserHelper {
return success;
}
public boolean showFileBrowserActivity(Fragment c, File startPath, int requestcode, FileBrowserFailOverCallback callback) {
boolean success = false;
if (startPath == null) {
startPath = new File(K9.getAttachmentDefaultPath());
}
int listIndex = 0;
do {
String intentAction = PICK_DIRECTORY_INTENTS[listIndex][0];
String uriPrefix = PICK_DIRECTORY_INTENTS[listIndex][1];
Intent intent = new Intent(intentAction);
intent.setData(Uri.parse(uriPrefix + startPath.getPath()));
try {
c.startActivityForResult(intent, requestcode);
success = true;
} catch (ActivityNotFoundException e) {
// Try the next intent in the list
listIndex++;
}
} while (!success && (listIndex < PICK_DIRECTORY_INTENTS.length));
if (listIndex == PICK_DIRECTORY_INTENTS.length) {
//No Filebrowser is installed => show a fallback textdialog
showPathTextInput(c.getActivity(), startPath, callback);
success = false;
}
return success;
}
private void showPathTextInput(final Activity c, final File startPath, final FileBrowserFailOverCallback callback) {
AlertDialog.Builder alert = new AlertDialog.Builder(c);

View File

@ -1,7 +1,7 @@
package com.fsck.k9.view;
import android.app.Activity;
import android.content.Context;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
@ -16,12 +16,10 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeUtility;
public class MessageCryptoView extends LinearLayout {
private Context mContext;
private Activity mActivity;
private Fragment mFragment;
private Button mDecryptButton;
private LinearLayout mCryptoSignatureLayout = null;
private ImageView mCryptoSignatureStatusImage = null;
@ -43,8 +41,8 @@ public class MessageCryptoView extends LinearLayout {
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
}
public void setActivity(Activity activity) {
mActivity = activity;
public void setFragment(Fragment fragment) {
mFragment = fragment;
}
@ -110,7 +108,7 @@ public class MessageCryptoView extends LinearLayout {
if (part != null) {
data = MimeUtility.getTextFromPart(part);
}
cryptoProvider.decrypt(mActivity, data, pgpData);
cryptoProvider.decrypt(mFragment, data, pgpData);
} catch (MessagingException me) {
Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
}

View File

@ -11,6 +11,7 @@ import android.os.AsyncTask;
import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.app.Fragment;
import android.util.AttributeSet;
import android.util.Log;
import android.view.ContextMenu;
@ -108,7 +109,8 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
private String mText;
public void initialize(Activity activity) {
public void initialize(Fragment fragment) {
Activity activity = fragment.getActivity();
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
mMessageContentView.configure();
@ -128,7 +130,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments);
mShowHiddenAttachments.setVisibility(View.GONE);
mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt);
mCryptoView.setActivity(activity);
mCryptoView.setFragment(fragment);
mCryptoView.setupChildViews();
mShowPicturesAction = findViewById(R.id.show_pictures);
mShowMessageAction = findViewById(R.id.show_message);
@ -157,7 +159,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
mHeaderPlaceHolder.removeView(mHeaderContainer);
// the HTC version of WebView tries to force the background of the
// titlebar, which is really unfair.
mHeaderContainer.setBackgroundColor(((K9Activity)activity).getThemeBackgroundColor());
// mHeaderContainer.setBackgroundColor(((K9Activity)activity).getThemeBackgroundColor());
mTitleBarHeaderContainer = new LinearLayout(activity);
mMessageContentView.setEmbeddedTitleBarCompat(mTitleBarHeaderContainer);