mirror of
https://github.com/moparisthebest/k-9
synced 2024-11-11 20:15:03 -05:00
Merge branch 'fragments'
This commit is contained in:
commit
d3f9633378
@ -1,47 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<merge xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/bottom_buttons"
|
|
||||||
android:orientation="horizontal"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="57dip"
|
|
||||||
android:background="@android:color/transparent"
|
|
||||||
android:gravity="center_vertical">
|
|
||||||
<Button
|
|
||||||
android:id="@+id/previous"
|
|
||||||
android:text="@string/message_view_prev_action"
|
|
||||||
android:contentDescription="@string/previous_action"
|
|
||||||
android:textSize="35dip"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:padding="0dip"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
<Button
|
|
||||||
android:id="@+id/reply"
|
|
||||||
android:text="@string/reply_action"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
<Button
|
|
||||||
android:id="@+id/delete"
|
|
||||||
android:text="@string/delete_action"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
<Button
|
|
||||||
android:id="@+id/forward"
|
|
||||||
android:text="@string/forward_action"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
<Button
|
|
||||||
android:id="@+id/next"
|
|
||||||
android:text="@string/message_view_next_action"
|
|
||||||
android:contentDescription="@string/next_action"
|
|
||||||
android:textSize="35dip"
|
|
||||||
android:padding="0dip"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_width="0dip"
|
|
||||||
android:layout_weight="1" />
|
|
||||||
</LinearLayout>
|
|
||||||
</merge>
|
|
@ -1,16 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:id="@+id/message_list_container"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent"
|
android:layout_height="fill_parent"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical" />
|
||||||
android:background="?android:attr/colorBackground">
|
|
||||||
<com.handmark.pulltorefresh.library.PullToRefreshListView
|
|
||||||
android:id="@+id/message_list"
|
|
||||||
android:layout_width="fill_parent"
|
|
||||||
android:layout_height="fill_parent"
|
|
||||||
android:cacheColorHint="?android:attr/colorBackground"
|
|
||||||
android:layout_weight="5"
|
|
||||||
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
9
res/layout/message_list_fragment.xml
Normal file
9
res/layout/message_list_fragment.xml
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.handmark.pulltorefresh.library.PullToRefreshListView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/message_list"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
android:layout_weight="5"
|
||||||
|
android:background="?android:attr/colorBackground"
|
||||||
|
android:cacheColorHint="?android:attr/colorBackground" />
|
@ -1,10 +1,8 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@id/message"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="fill_parent">
|
android:layout_height="fill_parent">
|
||||||
|
|
||||||
<include layout="@layout/message"/>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
68
res/menu/message_view_fragment.xml
Normal file
68
res/menu/message_view_fragment.xml
Normal 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>
|
@ -12,69 +12,4 @@
|
|||||||
android:icon="?attr/iconActionPreviousMessage"
|
android:icon="?attr/iconActionPreviousMessage"
|
||||||
android:showAsAction="always"
|
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>
|
</menu>
|
||||||
|
@ -1,230 +1,37 @@
|
|||||||
package com.fsck.k9.activity;
|
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.os.Bundle;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.view.animation.AccelerateInterpolator;
|
|
||||||
import android.view.animation.Animation;
|
|
||||||
import android.view.animation.TranslateAnimation;
|
|
||||||
|
|
||||||
import com.actionbarsherlock.app.SherlockActivity;
|
import com.actionbarsherlock.app.SherlockActivity;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.activity.K9ActivityCommon.K9ActivityMagic;
|
||||||
|
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||||
|
|
||||||
|
|
||||||
public class K9Activity extends SherlockActivity {
|
public class K9Activity extends SherlockActivity implements K9ActivityMagic {
|
||||||
protected static final int BEZEL_SWIPE_THRESHOLD = 20;
|
|
||||||
|
private K9ActivityCommon mBase;
|
||||||
|
|
||||||
protected GestureDetector mGestureDetector;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
setLanguage(this, K9.getK9Language());
|
mBase = K9ActivityCommon.newInstance(this);
|
||||||
setTheme(K9.getK9ThemeResourceId());
|
super.onCreate(savedInstanceState);
|
||||||
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
|
@Override
|
||||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||||
if (mGestureDetector != null) {
|
mBase.preDispatchTouchEvent(event);
|
||||||
mGestureDetector.onTouchEvent(ev);
|
return super.dispatchTouchEvent(event);
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() {
|
public int getThemeBackgroundColor() {
|
||||||
TypedArray array = getTheme().obtainStyledAttributes(new int[] {
|
return mBase.getThemeBackgroundColor();
|
||||||
android.R.attr.colorBackground,
|
|
||||||
});
|
|
||||||
int backgroundColor = array.getColor(0, 0xFF00FF);
|
|
||||||
array.recycle();
|
|
||||||
return backgroundColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupGestureDetector(OnSwipeGestureListener listener) {
|
||||||
|
mBase.setupGestureDetector(listener);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
115
src/com/fsck/k9/activity/K9ActivityCommon.java
Normal file
115
src/com/fsck/k9/activity/K9ActivityCommon.java
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
import com.fsck.k9.K9;
|
||||||
|
import com.fsck.k9.activity.misc.SwipeGestureDetector;
|
||||||
|
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||||
|
import com.fsck.k9.helper.StringUtils;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.res.Configuration;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.content.res.TypedArray;
|
||||||
|
import android.view.GestureDetector;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class implements functionality common to most activities used in K-9 Mail.
|
||||||
|
*
|
||||||
|
* @see K9Activity
|
||||||
|
* @see K9ListActivity
|
||||||
|
* @see K9FragmentActivity
|
||||||
|
*/
|
||||||
|
public class K9ActivityCommon {
|
||||||
|
/**
|
||||||
|
* Creates a new instance of {@link K9ActivityCommon} bound to the specified activity.
|
||||||
|
*
|
||||||
|
* @param activity
|
||||||
|
* The {@link Activity} the returned {@code K9ActivityCommon} instance will be bound to.
|
||||||
|
*
|
||||||
|
* @return The {@link K9ActivityCommon} instance that will provide the base functionality of the
|
||||||
|
* "K9" activities.
|
||||||
|
*/
|
||||||
|
public static K9ActivityCommon newInstance(Activity activity) {
|
||||||
|
return new K9ActivityCommon(activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void setLanguage(Activity activity, String language) {
|
||||||
|
Locale locale;
|
||||||
|
if (StringUtils.isNullOrEmpty(language)) {
|
||||||
|
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;
|
||||||
|
Resources resources = activity.getResources();
|
||||||
|
resources.updateConfiguration(config, resources.getDisplayMetrics());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base activities need to implement this interface.
|
||||||
|
*
|
||||||
|
* <p>The implementing class simply has to call through to the implementation of these methods
|
||||||
|
* in {@link K9ActivityCommon}.</p>
|
||||||
|
*/
|
||||||
|
public interface K9ActivityMagic {
|
||||||
|
int getThemeBackgroundColor();
|
||||||
|
void setupGestureDetector(OnSwipeGestureListener listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Activity mActivity;
|
||||||
|
private GestureDetector mGestureDetector;
|
||||||
|
|
||||||
|
|
||||||
|
private K9ActivityCommon(Activity activity) {
|
||||||
|
mActivity = activity;
|
||||||
|
setLanguage(mActivity, K9.getK9Language());
|
||||||
|
mActivity.setTheme(K9.getK9ThemeResourceId());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this before calling {@code super.dispatchTouchEvent(MotionEvent)}.
|
||||||
|
*/
|
||||||
|
public void preDispatchTouchEvent(MotionEvent event) {
|
||||||
|
if (mGestureDetector != null) {
|
||||||
|
mGestureDetector.onTouchEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the background color of the theme used for this activity.
|
||||||
|
*
|
||||||
|
* @return The background color of the current theme.
|
||||||
|
*/
|
||||||
|
public int getThemeBackgroundColor() {
|
||||||
|
TypedArray array = mActivity.getTheme().obtainStyledAttributes(
|
||||||
|
new int[] { android.R.attr.colorBackground });
|
||||||
|
|
||||||
|
int backgroundColor = array.getColor(0, 0xFF00FF);
|
||||||
|
|
||||||
|
array.recycle();
|
||||||
|
|
||||||
|
return backgroundColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call this if you wish to use the swipe gesture detector.
|
||||||
|
*
|
||||||
|
* @param listener
|
||||||
|
* A listener that will be notified if a left to right or right to left swipe has been
|
||||||
|
* detected.
|
||||||
|
*/
|
||||||
|
public void setupGestureDetector(OnSwipeGestureListener listener) {
|
||||||
|
mGestureDetector = new GestureDetector(mActivity,
|
||||||
|
new SwipeGestureDetector(mActivity, listener));
|
||||||
|
}
|
||||||
|
}
|
37
src/com/fsck/k9/activity/K9FragmentActivity.java
Normal file
37
src/com/fsck/k9/activity/K9FragmentActivity.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.view.MotionEvent;
|
||||||
|
|
||||||
|
import com.actionbarsherlock.app.SherlockFragmentActivity;
|
||||||
|
import com.fsck.k9.activity.K9ActivityCommon.K9ActivityMagic;
|
||||||
|
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||||
|
|
||||||
|
|
||||||
|
public class K9FragmentActivity extends SherlockFragmentActivity implements K9ActivityMagic {
|
||||||
|
|
||||||
|
private K9ActivityCommon mBase;
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
mBase = K9ActivityCommon.newInstance(this);
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||||
|
mBase.preDispatchTouchEvent(event);
|
||||||
|
return super.dispatchTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThemeBackgroundColor() {
|
||||||
|
return mBase.getThemeBackgroundColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupGestureDetector(OnSwipeGestureListener listener) {
|
||||||
|
mBase.setupGestureDetector(listener);
|
||||||
|
}
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
package com.fsck.k9.activity;
|
package com.fsck.k9.activity;
|
||||||
|
|
||||||
import android.util.Log;
|
import java.text.DateFormat;
|
||||||
import android.view.GestureDetector;
|
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
import android.widget.AdapterView;
|
import android.widget.AdapterView;
|
||||||
@ -10,94 +10,96 @@ import android.os.Bundle;
|
|||||||
|
|
||||||
import com.actionbarsherlock.app.SherlockListActivity;
|
import com.actionbarsherlock.app.SherlockListActivity;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
|
import com.fsck.k9.activity.K9ActivityCommon.K9ActivityMagic;
|
||||||
|
import com.fsck.k9.activity.misc.SwipeGestureDetector.OnSwipeGestureListener;
|
||||||
import com.fsck.k9.helper.DateFormatter;
|
import com.fsck.k9.helper.DateFormatter;
|
||||||
|
|
||||||
public class K9ListActivity extends SherlockListActivity {
|
|
||||||
protected GestureDetector mGestureDetector;
|
public class K9ListActivity extends SherlockListActivity implements K9ActivityMagic {
|
||||||
|
|
||||||
|
private K9ActivityCommon mBase;
|
||||||
|
private DateFormat mDateFormat;
|
||||||
|
private DateFormat mTimeFormat;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
K9Activity.setLanguage(this, K9.getK9Language());
|
mBase = K9ActivityCommon.newInstance(this);
|
||||||
setTheme(K9.getK9ThemeResourceId());
|
super.onCreate(savedInstanceState);
|
||||||
super.onCreate(icicle);
|
|
||||||
setupFormats();
|
setupFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean dispatchTouchEvent(MotionEvent event) {
|
||||||
|
mBase.preDispatchTouchEvent(event);
|
||||||
|
return super.dispatchTouchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
setupFormats();
|
setupFormats();
|
||||||
}
|
}
|
||||||
|
|
||||||
private java.text.DateFormat mDateFormat;
|
public DateFormat getDateFormat() {
|
||||||
private java.text.DateFormat mTimeFormat;
|
return mDateFormat;
|
||||||
|
|
||||||
private void setupFormats() {
|
|
||||||
mDateFormat = DateFormatter.getDateFormat(this);
|
|
||||||
mTimeFormat = android.text.format.DateFormat.getTimeFormat(this); // 12/24 date format
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public java.text.DateFormat getTimeFormat() {
|
public DateFormat getTimeFormat() {
|
||||||
return mTimeFormat;
|
return mTimeFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
public java.text.DateFormat getDateFormat() {
|
private void setupFormats() {
|
||||||
return mDateFormat;
|
mTimeFormat = android.text.format.DateFormat.getTimeFormat(this);
|
||||||
|
mDateFormat = DateFormatter.getDateFormat(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getThemeBackgroundColor() {
|
||||||
|
return mBase.getThemeBackgroundColor();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupGestureDetector(OnSwipeGestureListener listener) {
|
||||||
|
mBase.setupGestureDetector(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
public boolean onKeyDown(int keyCode, KeyEvent event) {
|
||||||
// Shortcuts that work no matter what is selected
|
// Shortcuts that work no matter what is selected
|
||||||
switch (keyCode) {
|
if (K9.useVolumeKeysForListNavigationEnabled() &&
|
||||||
case KeyEvent.KEYCODE_VOLUME_UP: {
|
(keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
||||||
|
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
|
||||||
|
|
||||||
final ListView listView = getListView();
|
final ListView listView = getListView();
|
||||||
if (K9.useVolumeKeysForListNavigationEnabled()) {
|
|
||||||
int currentPosition = listView.getSelectedItemPosition();
|
|
||||||
if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode()) {
|
|
||||||
currentPosition = listView.getFirstVisiblePosition();
|
|
||||||
}
|
|
||||||
if (currentPosition > 0) {
|
|
||||||
listView.setSelection(currentPosition - 1);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case KeyEvent.KEYCODE_VOLUME_DOWN: {
|
|
||||||
final ListView listView = getListView();
|
|
||||||
if (K9.useVolumeKeysForListNavigationEnabled()) {
|
|
||||||
int currentPosition = listView.getSelectedItemPosition();
|
int currentPosition = listView.getSelectedItemPosition();
|
||||||
if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode()) {
|
if (currentPosition == AdapterView.INVALID_POSITION || listView.isInTouchMode()) {
|
||||||
currentPosition = listView.getFirstVisiblePosition();
|
currentPosition = listView.getFirstVisiblePosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentPosition < listView.getCount()) {
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP && currentPosition > 0) {
|
||||||
|
listView.setSelection(currentPosition - 1);
|
||||||
|
} else if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN &&
|
||||||
|
currentPosition < listView.getCount()) {
|
||||||
listView.setSelection(currentPosition + 1);
|
listView.setSelection(currentPosition + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
return super.onKeyDown(keyCode, event);
|
return super.onKeyDown(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
public boolean onKeyUp(int keyCode, KeyEvent event) {
|
||||||
// Swallow these events too to avoid the audible notification of a volume change
|
// Swallow these events too to avoid the audible notification of a volume change
|
||||||
if (K9.useVolumeKeysForListNavigationEnabled()) {
|
if (K9.useVolumeKeysForListNavigationEnabled() &&
|
||||||
if ((keyCode == KeyEvent.KEYCODE_VOLUME_UP) || (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
|
(keyCode == KeyEvent.KEYCODE_VOLUME_UP ||
|
||||||
if (K9.DEBUG)
|
keyCode == KeyEvent.KEYCODE_VOLUME_DOWN)) {
|
||||||
Log.v(K9.LOG_TAG, "Swallowed key up.");
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return super.onKeyUp(keyCode, event);
|
return super.onKeyUp(keyCode, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean dispatchTouchEvent(MotionEvent ev) {
|
|
||||||
if (mGestureDetector != null) {
|
|
||||||
mGestureDetector.onTouchEvent(ev);
|
|
||||||
}
|
|
||||||
return super.dispatchTouchEvent(ev);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import android.preference.Preference;
|
|||||||
public class K9PreferenceActivity extends SherlockPreferenceActivity {
|
public class K9PreferenceActivity extends SherlockPreferenceActivity {
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle icicle) {
|
public void onCreate(Bundle icicle) {
|
||||||
K9Activity.setLanguage(this, K9.getK9Language());
|
K9ActivityCommon.setLanguage(this, K9.getK9Language());
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= 6 && Build.VERSION.SDK_INT < 14) {
|
if (Build.VERSION.SDK_INT >= 6 && Build.VERSION.SDK_INT < 14) {
|
||||||
// There's a display bug in all supported Android versions before 4.0 (SDK 14) which
|
// There's a display bug in all supported Android versions before 4.0 (SDK 14) which
|
||||||
@ -88,5 +88,4 @@ public class K9PreferenceActivity extends SherlockPreferenceActivity {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -440,12 +440,11 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc
|
|||||||
* Save will attempt to replace the message in the given folder with the updated version.
|
* Save will attempt to replace the message in the given folder with the updated version.
|
||||||
* Discard will delete the message from the given folder.
|
* Discard will delete the message from the given folder.
|
||||||
* @param context
|
* @param context
|
||||||
* @param account
|
|
||||||
* @param message
|
* @param message
|
||||||
*/
|
*/
|
||||||
public static void actionEditDraft(Context context, Account account, Message message) {
|
public static void actionEditDraft(Context context, MessageReference messageReference) {
|
||||||
Intent i = new Intent(context, MessageCompose.class);
|
Intent i = new Intent(context, MessageCompose.class);
|
||||||
i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference());
|
i.putExtra(EXTRA_MESSAGE_REFERENCE, messageReference);
|
||||||
i.setAction(ACTION_EDIT_DRAFT);
|
i.setAction(ACTION_EDIT_DRAFT);
|
||||||
context.startActivity(i);
|
context.startActivity(i);
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -2,18 +2,23 @@ package com.fsck.k9.activity.misc;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.view.MotionEvent;
|
import android.view.MotionEvent;
|
||||||
|
import android.view.GestureDetector.OnGestureListener;
|
||||||
import android.view.GestureDetector.SimpleOnGestureListener;
|
import android.view.GestureDetector.SimpleOnGestureListener;
|
||||||
|
|
||||||
|
|
||||||
public class SwipeGestureDetector extends SimpleOnGestureListener {
|
public class SwipeGestureDetector extends SimpleOnGestureListener {
|
||||||
|
public static final int BEZEL_SWIPE_THRESHOLD = 20;
|
||||||
|
|
||||||
private static final float SWIPE_MAX_OFF_PATH_DIP = 250f;
|
private static final float SWIPE_MAX_OFF_PATH_DIP = 250f;
|
||||||
private static final float SWIPE_THRESHOLD_VELOCITY_DIP = 325f;
|
private static final float SWIPE_THRESHOLD_VELOCITY_DIP = 325f;
|
||||||
|
|
||||||
|
|
||||||
private final OnSwipeGestureListener mListener;
|
private final OnSwipeGestureListener mListener;
|
||||||
private int mMinVelocity;
|
private int mMinVelocity;
|
||||||
private int mMaxOffPath;
|
private int mMaxOffPath;
|
||||||
private MotionEvent mLastOnDownEvent = null;
|
private MotionEvent mLastOnDownEvent = null;
|
||||||
|
|
||||||
|
|
||||||
public SwipeGestureDetector(Context context, OnSwipeGestureListener listener) {
|
public SwipeGestureDetector(Context context, OnSwipeGestureListener listener) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
@ -80,8 +85,35 @@ public class SwipeGestureDetector extends SimpleOnGestureListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A listener that will be notified when a right to left or left to right swipe has been
|
||||||
|
* detected.
|
||||||
|
*/
|
||||||
public interface OnSwipeGestureListener {
|
public interface OnSwipeGestureListener {
|
||||||
|
/**
|
||||||
|
* Called when a swipe from right to left is handled by {@link MyGestureDetector}.
|
||||||
|
*
|
||||||
|
* <p>See {@link OnGestureListener#onFling(MotionEvent, MotionEvent, float, float)}
|
||||||
|
* for more information on the {@link MotionEvent}s being passed.</p>
|
||||||
|
*
|
||||||
|
* @param e1
|
||||||
|
* First down motion event that started the fling.
|
||||||
|
* @param e2
|
||||||
|
* The move motion event that triggered the current onFling.
|
||||||
|
*/
|
||||||
void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2);
|
void onSwipeRightToLeft(final MotionEvent e1, final MotionEvent e2);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when a swipe from left to right is handled by {@link MyGestureDetector}.
|
||||||
|
*
|
||||||
|
* <p>See {@link OnGestureListener#onFling(MotionEvent, MotionEvent, float, float)}
|
||||||
|
* for more information on the {@link MotionEvent}s being passed.</p>
|
||||||
|
*
|
||||||
|
* @param e1
|
||||||
|
* First down motion event that started the fling.
|
||||||
|
* @param e2
|
||||||
|
* The move motion event that triggered the current onFling.
|
||||||
|
*/
|
||||||
void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2);
|
void onSwipeLeftToRight(final MotionEvent e1, final MotionEvent e2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,11 +13,11 @@ import android.content.pm.PackageInfo;
|
|||||||
import android.content.pm.PackageManager.NameNotFoundException;
|
import android.content.pm.PackageManager.NameNotFoundException;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.activity.MessageCompose;
|
import com.fsck.k9.activity.MessageCompose;
|
||||||
import com.fsck.k9.activity.MessageView;
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
import com.fsck.k9.mail.MessagingException;
|
import com.fsck.k9.mail.MessagingException;
|
||||||
import com.fsck.k9.mail.Part;
|
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 String INTENT_VERSION = "1";
|
||||||
|
|
||||||
public static final int DECRYPT_MESSAGE = 0x21070001;
|
// Note: The support package only allows us to use the lower 16 bits of a request code.
|
||||||
public static final int ENCRYPT_MESSAGE = 0x21070002;
|
public static final int DECRYPT_MESSAGE = 0x0000A001;
|
||||||
public static final int SELECT_PUBLIC_KEYS = 0x21070003;
|
public static final int ENCRYPT_MESSAGE = 0x0000A002;
|
||||||
public static final int SELECT_SECRET_KEY = 0x21070004;
|
public static final int SELECT_PUBLIC_KEYS = 0x0000A003;
|
||||||
|
public static final int SELECT_SECRET_KEY = 0x0000A004;
|
||||||
|
|
||||||
public static Pattern PGP_MESSAGE =
|
public static Pattern PGP_MESSAGE =
|
||||||
Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
|
Pattern.compile(".*?(-----BEGIN PGP MESSAGE-----.*?-----END PGP MESSAGE-----).*",
|
||||||
@ -406,7 +407,19 @@ public class Apg extends CryptoProvider {
|
|||||||
}
|
}
|
||||||
break;
|
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) {
|
if (resultCode != Activity.RESULT_OK || data == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -417,13 +430,14 @@ public class Apg extends CryptoProvider {
|
|||||||
pgpData.setSignatureUnknown(data.getBooleanExtra(Apg.EXTRA_SIGNATURE_UNKNOWN, false));
|
pgpData.setSignatureUnknown(data.getBooleanExtra(Apg.EXTRA_SIGNATURE_UNKNOWN, false));
|
||||||
|
|
||||||
pgpData.setDecryptedData(data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE));
|
pgpData.setDecryptedData(data.getStringExtra(Apg.EXTRA_DECRYPTED_MESSAGE));
|
||||||
((MessageView) activity).onDecryptDone(pgpData);
|
callback.onDecryptDone(pgpData);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
}
|
||||||
default:
|
default: {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -458,13 +472,13 @@ public class Apg extends CryptoProvider {
|
|||||||
/**
|
/**
|
||||||
* Start the decrypt activity.
|
* Start the decrypt activity.
|
||||||
*
|
*
|
||||||
* @param activity
|
* @param fragment
|
||||||
* @param data
|
* @param data
|
||||||
* @param pgpData
|
* @param pgpData
|
||||||
* @return success or failure
|
* @return success or failure
|
||||||
*/
|
*/
|
||||||
@Override
|
@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);
|
android.content.Intent intent = new android.content.Intent(Apg.Intent.DECRYPT_AND_RETURN);
|
||||||
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
|
intent.putExtra(EXTRA_INTENT_VERSION, INTENT_VERSION);
|
||||||
intent.setType("text/plain");
|
intent.setType("text/plain");
|
||||||
@ -473,10 +487,10 @@ public class Apg extends CryptoProvider {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
intent.putExtra(EXTRA_TEXT, data);
|
intent.putExtra(EXTRA_TEXT, data);
|
||||||
activity.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
|
fragment.startActivityForResult(intent, Apg.DECRYPT_MESSAGE);
|
||||||
return true;
|
return true;
|
||||||
} catch (ActivityNotFoundException e) {
|
} 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;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package com.fsck.k9.crypto;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
|
|
||||||
@ -19,10 +20,12 @@ abstract public class CryptoProvider {
|
|||||||
abstract public boolean isSigned(Message message);
|
abstract public boolean isSigned(Message message);
|
||||||
abstract public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
|
abstract public boolean onActivityResult(Activity activity, int requestCode, int resultCode,
|
||||||
Intent data, PgpData pgpData);
|
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 selectSecretKey(Activity activity, PgpData pgpData);
|
||||||
abstract public boolean selectEncryptionKeys(Activity activity, String emails, 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 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[] getSecretKeyIdsFromEmail(Context context, String email);
|
||||||
abstract public long[] getPublicKeyIdsFromEmail(Context context, String email);
|
abstract public long[] getPublicKeyIdsFromEmail(Context context, String email);
|
||||||
abstract public boolean hasSecretKeyForEmail(Context context, String email);
|
abstract public boolean hasSecretKeyForEmail(Context context, String email);
|
||||||
@ -38,4 +41,8 @@ abstract public class CryptoProvider {
|
|||||||
|
|
||||||
return None.createInstance();
|
return None.createInstance();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public interface CryptoDecryptCallback {
|
||||||
|
void onDecryptDone(PgpData pgpData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ package com.fsck.k9.crypto;
|
|||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
|
|
||||||
import com.fsck.k9.mail.Message;
|
import com.fsck.k9.mail.Message;
|
||||||
|
|
||||||
@ -63,13 +65,19 @@ public class None extends CryptoProvider {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onDecryptActivityResult(CryptoDecryptCallback callback, int requestCode,
|
||||||
|
int resultCode, Intent data, PgpData pgpData) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean encrypt(Activity activity, String data, PgpData pgpData) {
|
public boolean encrypt(Activity activity, String data, PgpData pgpData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean decrypt(Activity activity, String data, PgpData pgpData) {
|
public boolean decrypt(Fragment fragment, String data, PgpData pgpData) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
95
src/com/fsck/k9/fragment/ConfirmationDialogFragment.java
Normal file
95
src/com/fsck/k9/fragment/ConfirmationDialogFragment.java
Normal 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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
3044
src/com/fsck/k9/fragment/MessageListFragment.java
Normal file
3044
src/com/fsck/k9/fragment/MessageListFragment.java
Normal file
File diff suppressed because it is too large
Load Diff
915
src/com/fsck/k9/fragment/MessageViewFragment.java
Normal file
915
src/com/fsck/k9/fragment/MessageViewFragment.java
Normal file
@ -0,0 +1,915 @@
|
|||||||
|
package com.fsck.k9.fragment;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.Intent;
|
||||||
|
import android.content.SharedPreferences.Editor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.support.v4.app.DialogFragment;
|
||||||
|
import android.support.v4.app.FragmentManager;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
|
import android.view.View;
|
||||||
|
import android.view.View.OnClickListener;
|
||||||
|
import android.view.ViewGroup;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
|
import com.actionbarsherlock.app.SherlockFragment;
|
||||||
|
import com.actionbarsherlock.view.Menu;
|
||||||
|
import com.actionbarsherlock.view.MenuInflater;
|
||||||
|
import com.actionbarsherlock.view.MenuItem;
|
||||||
|
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.ChooseFolder;
|
||||||
|
import com.fsck.k9.activity.MessageReference;
|
||||||
|
import com.fsck.k9.controller.MessagingController;
|
||||||
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
|
import com.fsck.k9.crypto.CryptoProvider.CryptoDecryptCallback;
|
||||||
|
import com.fsck.k9.crypto.PgpData;
|
||||||
|
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener;
|
||||||
|
import com.fsck.k9.helper.FileBrowserHelper;
|
||||||
|
import com.fsck.k9.helper.FileBrowserHelper.FileBrowserFailOverCallback;
|
||||||
|
import com.fsck.k9.mail.Flag;
|
||||||
|
import com.fsck.k9.mail.Message;
|
||||||
|
import com.fsck.k9.mail.MessagingException;
|
||||||
|
import com.fsck.k9.mail.Part;
|
||||||
|
import com.fsck.k9.mail.store.LocalStore.LocalMessage;
|
||||||
|
import com.fsck.k9.view.AttachmentView;
|
||||||
|
import com.fsck.k9.view.AttachmentView.AttachmentFileDownloadCallback;
|
||||||
|
import com.fsck.k9.view.MessageHeader;
|
||||||
|
import com.fsck.k9.view.SingleMessageView;
|
||||||
|
|
||||||
|
|
||||||
|
public class MessageViewFragment extends SherlockFragment implements OnClickListener,
|
||||||
|
CryptoDecryptCallback, ConfirmationDialogFragmentListener {
|
||||||
|
|
||||||
|
private static final String ARG_REFERENCE = "reference";
|
||||||
|
|
||||||
|
private static final String STATE_MESSAGE_REFERENCE = "reference";
|
||||||
|
private static final String STATE_PGP_DATA = "pgpData";
|
||||||
|
|
||||||
|
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
|
||||||
|
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
|
||||||
|
private static final int ACTIVITY_CHOOSE_DIRECTORY = 3;
|
||||||
|
|
||||||
|
|
||||||
|
public static MessageViewFragment newInstance(MessageReference reference) {
|
||||||
|
MessageViewFragment fragment = new MessageViewFragment();
|
||||||
|
|
||||||
|
Bundle args = new Bundle();
|
||||||
|
args.putParcelable(ARG_REFERENCE, reference);
|
||||||
|
fragment.setArguments(args);
|
||||||
|
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private SingleMessageView mMessageView;
|
||||||
|
private PgpData mPgpData;
|
||||||
|
private Menu mMenu;
|
||||||
|
private Account mAccount;
|
||||||
|
private MessageReference mMessageReference;
|
||||||
|
private Message mMessage;
|
||||||
|
private MessagingController mController;
|
||||||
|
private Listener mListener = new Listener();
|
||||||
|
private MessageViewHandler mHandler = new MessageViewHandler();
|
||||||
|
|
||||||
|
private MenuItem mToggleMessageViewMenu;
|
||||||
|
|
||||||
|
/** this variable is used to save the calling AttachmentView
|
||||||
|
* until the onActivityResult is called.
|
||||||
|
* => with this reference we can identity the caller
|
||||||
|
*/
|
||||||
|
private AttachmentView attachmentTmpStore;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used to temporarily store the destination folder for refile operations if a confirmation
|
||||||
|
* dialog is shown.
|
||||||
|
*/
|
||||||
|
private String mDstFolder;
|
||||||
|
|
||||||
|
private MessageViewFragmentListener mFragmentListener;
|
||||||
|
|
||||||
|
|
||||||
|
class MessageViewHandler extends Handler {
|
||||||
|
|
||||||
|
public void progress(final boolean progress) {
|
||||||
|
post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
setProgress(progress);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addAttachment(final View attachmentView) {
|
||||||
|
post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
mMessageView.addAttachment(attachmentView);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A helper for a set of "show a toast" methods */
|
||||||
|
private void showToast(final String message, final int toastLength) {
|
||||||
|
post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
Toast.makeText(getActivity(), message, toastLength).show();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void networkError() {
|
||||||
|
showToast(getString(R.string.status_network_error), Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void invalidIdError() {
|
||||||
|
showToast(getString(R.string.status_invalid_id_error), Toast.LENGTH_LONG);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public void fetchingAttachment() {
|
||||||
|
showToast(getString(R.string.message_view_fetching_attachment_toast), Toast.LENGTH_SHORT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAttach(Activity activity) {
|
||||||
|
super.onAttach(activity);
|
||||||
|
|
||||||
|
try {
|
||||||
|
mFragmentListener = (MessageViewFragmentListener) activity;
|
||||||
|
} catch (ClassCastException e) {
|
||||||
|
throw new ClassCastException(activity.getClass() +
|
||||||
|
" must implement MessageViewFragmentListener");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
|
// This fragments adds options to the action bar
|
||||||
|
setHasOptionsMenu(true);
|
||||||
|
|
||||||
|
mController = MessagingController.getInstance(getActivity().getApplication());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public View onCreateView(LayoutInflater inflater, ViewGroup container,
|
||||||
|
Bundle savedInstanceState) {
|
||||||
|
|
||||||
|
View view = inflater.inflate(R.layout.message, container, false);
|
||||||
|
|
||||||
|
|
||||||
|
mMessageView = (SingleMessageView) view.findViewById(R.id.message_view);
|
||||||
|
|
||||||
|
//set a callback for the attachment view. With this callback the attachmentview
|
||||||
|
//request the start of a filebrowser activity.
|
||||||
|
mMessageView.setAttachmentCallback(new AttachmentFileDownloadCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void showFileBrowser(final AttachmentView caller) {
|
||||||
|
FileBrowserHelper.getInstance()
|
||||||
|
.showFileBrowserActivity(MessageViewFragment.this,
|
||||||
|
null,
|
||||||
|
ACTIVITY_CHOOSE_DIRECTORY,
|
||||||
|
callback);
|
||||||
|
attachmentTmpStore = caller;
|
||||||
|
}
|
||||||
|
|
||||||
|
FileBrowserFailOverCallback callback = new FileBrowserFailOverCallback() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPathEntered(String path) {
|
||||||
|
attachmentTmpStore.writeFile(new File(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCancel() {
|
||||||
|
// canceled, do nothing
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
mMessageView.initialize(this);
|
||||||
|
mMessageView.downloadRemainderButton().setOnClickListener(this);
|
||||||
|
|
||||||
|
mFragmentListener.messageHeaderViewAvailable(mMessageView.getMessageHeaderView());
|
||||||
|
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityCreated(Bundle savedInstanceState) {
|
||||||
|
super.onActivityCreated(savedInstanceState);
|
||||||
|
|
||||||
|
MessageReference messageReference;
|
||||||
|
if (savedInstanceState != null) {
|
||||||
|
mPgpData = (PgpData) savedInstanceState.get(STATE_PGP_DATA);
|
||||||
|
messageReference = (MessageReference) savedInstanceState.get(STATE_MESSAGE_REFERENCE);
|
||||||
|
} else {
|
||||||
|
Bundle args = getArguments();
|
||||||
|
messageReference = (MessageReference) args.getParcelable(ARG_REFERENCE);
|
||||||
|
}
|
||||||
|
|
||||||
|
displayMessage(messageReference, (mPgpData == null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onSaveInstanceState(Bundle outState) {
|
||||||
|
super.onSaveInstanceState(outState);
|
||||||
|
outState.putParcelable(STATE_MESSAGE_REFERENCE, mMessageReference);
|
||||||
|
outState.putSerializable(STATE_PGP_DATA, mPgpData);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void displayMessage(MessageReference ref) {
|
||||||
|
displayMessage(ref, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayMessage(MessageReference ref, boolean resetPgpData) {
|
||||||
|
mMessageReference = ref;
|
||||||
|
if (K9.DEBUG) {
|
||||||
|
Log.d(K9.LOG_TAG, "MessageView displaying message " + mMessageReference);
|
||||||
|
}
|
||||||
|
|
||||||
|
Context appContext = getActivity().getApplicationContext();
|
||||||
|
mAccount = Preferences.getPreferences(appContext).getAccount(mMessageReference.accountUuid);
|
||||||
|
|
||||||
|
if (resetPgpData) {
|
||||||
|
// start with fresh, empty PGP data
|
||||||
|
mPgpData = new PgpData();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear previous message
|
||||||
|
mMessageView.resetView();
|
||||||
|
mMessageView.resetHeaderView();
|
||||||
|
|
||||||
|
mController.loadMessageForView(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener);
|
||||||
|
configureMenu(mMenu);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called from UI thread when user select Delete
|
||||||
|
*/
|
||||||
|
public void onDelete() {
|
||||||
|
if (K9.confirmDelete() || (K9.confirmDeleteStarred() && mMessage.isSet(Flag.FLAGGED))) {
|
||||||
|
showDialog(R.id.dialog_confirm_delete);
|
||||||
|
} else {
|
||||||
|
delete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
// Disable the delete button after it's tapped (to try to prevent
|
||||||
|
// accidental clicks)
|
||||||
|
mMenu.findItem(R.id.delete).setEnabled(false);
|
||||||
|
Message messageToDelete = mMessage;
|
||||||
|
mFragmentListener.showNextMessageOrReturn();
|
||||||
|
mController.deleteMessages(new Message[] {messageToDelete}, null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onRefile(String dstFolder) {
|
||||||
|
if (!mController.isMoveCapable(mAccount)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mController.isMoveCapable(mMessage)) {
|
||||||
|
Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
|
||||||
|
toast.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (K9.FOLDER_NONE.equalsIgnoreCase(dstFolder)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mAccount.getSpamFolderName().equals(dstFolder) && K9.confirmSpam()) {
|
||||||
|
mDstFolder = dstFolder;
|
||||||
|
showDialog(R.id.dialog_confirm_spam);
|
||||||
|
} else {
|
||||||
|
refileMessage(dstFolder);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void refileMessage(String dstFolder) {
|
||||||
|
String srcFolder = mMessageReference.folderName;
|
||||||
|
Message messageToMove = mMessage;
|
||||||
|
mFragmentListener.showNextMessageOrReturn();
|
||||||
|
mController.moveMessage(mAccount, srcFolder, messageToMove, dstFolder, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReply() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
mFragmentListener.onReply(mMessage, mPgpData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onReplyAll() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
mFragmentListener.onReplyAll(mMessage, mPgpData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onForward() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
mFragmentListener.onForward(mMessage, mPgpData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onFlag() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
boolean newState = !mMessage.isSet(Flag.FLAGGED);
|
||||||
|
mController.setFlag(mAccount, mMessage.getFolder().getName(),
|
||||||
|
new Message[] { mMessage }, Flag.FLAGGED, newState);
|
||||||
|
mMessageView.setHeaders(mMessage, mAccount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onMove() {
|
||||||
|
if ((!mController.isMoveCapable(mAccount))
|
||||||
|
|| (mMessage == null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mController.isMoveCapable(mMessage)) {
|
||||||
|
Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
|
||||||
|
toast.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startRefileActivity(ACTIVITY_CHOOSE_FOLDER_MOVE);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onCopy() {
|
||||||
|
if ((!mController.isCopyCapable(mAccount))
|
||||||
|
|| (mMessage == null)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!mController.isCopyCapable(mMessage)) {
|
||||||
|
Toast toast = Toast.makeText(getActivity(), R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
|
||||||
|
toast.show();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
startRefileActivity(ACTIVITY_CHOOSE_FOLDER_COPY);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onToggleColors() {
|
||||||
|
if (K9.getK9MessageViewTheme() == K9.THEME_DARK) {
|
||||||
|
K9.setK9MessageViewTheme(K9.THEME_LIGHT);
|
||||||
|
} else {
|
||||||
|
K9.setK9MessageViewTheme(K9.THEME_DARK);
|
||||||
|
}
|
||||||
|
|
||||||
|
new AsyncTask<Object, Object, Object>() {
|
||||||
|
@Override
|
||||||
|
protected Object doInBackground(Object... params) {
|
||||||
|
Context appContext = getActivity().getApplicationContext();
|
||||||
|
Preferences prefs = Preferences.getPreferences(appContext);
|
||||||
|
Editor editor = prefs.getPreferences().edit();
|
||||||
|
K9.save(editor);
|
||||||
|
editor.commit();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}.execute();
|
||||||
|
|
||||||
|
mFragmentListener.restartActivity();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRefileActivity(int activity) {
|
||||||
|
Intent intent = new Intent(getActivity(), ChooseFolder.class);
|
||||||
|
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid());
|
||||||
|
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, mMessageReference.folderName);
|
||||||
|
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, mAccount.getLastSelectedFolderName());
|
||||||
|
intent.putExtra(ChooseFolder.EXTRA_MESSAGE, mMessageReference);
|
||||||
|
startActivityForResult(intent, activity);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||||
|
if (mAccount.getCryptoProvider().onDecryptActivityResult(this, requestCode, resultCode, data, mPgpData)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resultCode != Activity.RESULT_OK) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (requestCode) {
|
||||||
|
case ACTIVITY_CHOOSE_DIRECTORY: {
|
||||||
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
|
// obtain the filename
|
||||||
|
Uri fileUri = data.getData();
|
||||||
|
if (fileUri != null) {
|
||||||
|
String filePath = fileUri.getPath();
|
||||||
|
if (filePath != null) {
|
||||||
|
attachmentTmpStore.writeFile(new File(filePath));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTIVITY_CHOOSE_FOLDER_MOVE:
|
||||||
|
case ACTIVITY_CHOOSE_FOLDER_COPY: {
|
||||||
|
if (data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
|
||||||
|
MessageReference ref = data.getParcelableExtra(ChooseFolder.EXTRA_MESSAGE);
|
||||||
|
if (mMessageReference.equals(ref)) {
|
||||||
|
mAccount.setLastSelectedFolderName(destFolderName);
|
||||||
|
switch (requestCode) {
|
||||||
|
case ACTIVITY_CHOOSE_FOLDER_MOVE: {
|
||||||
|
mFragmentListener.showNextMessageOrReturn();
|
||||||
|
moveMessage(ref, destFolderName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ACTIVITY_CHOOSE_FOLDER_COPY: {
|
||||||
|
copyMessage(ref, destFolderName);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onSendAlternate() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
mController.sendAlternate(getActivity(), mAccount, mMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onToggleRead() {
|
||||||
|
if (mMessage != null) {
|
||||||
|
mController.setFlag(mAccount, mMessage.getFolder().getName(),
|
||||||
|
new Message[] { mMessage }, Flag.SEEN, !mMessage.isSet(Flag.SEEN));
|
||||||
|
mMessageView.setHeaders(mMessage, mAccount);
|
||||||
|
String subject = mMessage.getSubject();
|
||||||
|
displayMessageSubject(subject);
|
||||||
|
updateUnreadToggleTitle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void onDownloadRemainder() {
|
||||||
|
if (mMessage.isSet(Flag.X_DOWNLOADED_FULL)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mMessageView.downloadRemainderButton().setEnabled(false);
|
||||||
|
mController.loadMessageForViewRemote(mAccount, mMessageReference.folderName, mMessageReference.uid, mListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onClick(View view) {
|
||||||
|
switch (view.getId()) {
|
||||||
|
case R.id.download: {
|
||||||
|
((AttachmentView)view).saveFile();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case R.id.download_remainder: {
|
||||||
|
onDownloadRemainder();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
switch (item.getItemId()) {
|
||||||
|
case R.id.delete:
|
||||||
|
onDelete();
|
||||||
|
break;
|
||||||
|
case R.id.reply:
|
||||||
|
onReply();
|
||||||
|
break;
|
||||||
|
case R.id.reply_all:
|
||||||
|
onReplyAll();
|
||||||
|
break;
|
||||||
|
case R.id.forward:
|
||||||
|
onForward();
|
||||||
|
break;
|
||||||
|
case R.id.share:
|
||||||
|
onSendAlternate();
|
||||||
|
break;
|
||||||
|
case R.id.toggle_unread:
|
||||||
|
onToggleRead();
|
||||||
|
break;
|
||||||
|
case R.id.archive:
|
||||||
|
onRefile(mAccount.getArchiveFolderName());
|
||||||
|
break;
|
||||||
|
case R.id.spam:
|
||||||
|
onRefile(mAccount.getSpamFolderName());
|
||||||
|
break;
|
||||||
|
case R.id.move:
|
||||||
|
onMove();
|
||||||
|
break;
|
||||||
|
case R.id.copy:
|
||||||
|
onCopy();
|
||||||
|
break;
|
||||||
|
case R.id.select_text:
|
||||||
|
mMessageView.beginSelectingText();
|
||||||
|
break;
|
||||||
|
case R.id.toggle_message_view_theme:
|
||||||
|
onToggleColors();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return super.onOptionsItemSelected(item);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
|
||||||
|
inflater.inflate(R.menu.message_view_fragment, menu);
|
||||||
|
mMenu = menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureMenu(Menu menu) {
|
||||||
|
// first run displayMessage() gets called before onCreateOptionMenu()
|
||||||
|
if (menu == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable them all
|
||||||
|
menu.findItem(R.id.copy).setVisible(true);
|
||||||
|
menu.findItem(R.id.move).setVisible(true);
|
||||||
|
menu.findItem(R.id.archive).setVisible(true);
|
||||||
|
menu.findItem(R.id.spam).setVisible(true);
|
||||||
|
|
||||||
|
mToggleMessageViewMenu = menu.findItem(R.id.toggle_message_view_theme);
|
||||||
|
if (K9.getK9MessageViewTheme() == K9.THEME_DARK) {
|
||||||
|
mToggleMessageViewMenu.setTitle(R.string.message_view_theme_action_light);
|
||||||
|
} else {
|
||||||
|
mToggleMessageViewMenu.setTitle(R.string.message_view_theme_action_dark);
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleActionsState(menu, true);
|
||||||
|
|
||||||
|
updateUnreadToggleTitle();
|
||||||
|
|
||||||
|
// comply with the setting
|
||||||
|
if (!mAccount.getEnableMoveButtons()) {
|
||||||
|
menu.findItem(R.id.move).setVisible(false);
|
||||||
|
menu.findItem(R.id.archive).setVisible(false);
|
||||||
|
menu.findItem(R.id.spam).setVisible(false);
|
||||||
|
} else {
|
||||||
|
// check message, folder capability
|
||||||
|
if (!mController.isCopyCapable(mAccount)) {
|
||||||
|
menu.findItem(R.id.copy).setVisible(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mController.isMoveCapable(mAccount)) {
|
||||||
|
menu.findItem(R.id.move).setVisible(true);
|
||||||
|
|
||||||
|
menu.findItem(R.id.archive).setVisible(
|
||||||
|
!mMessageReference.folderName.equals(mAccount.getArchiveFolderName())
|
||||||
|
&& mAccount.hasArchiveFolder());
|
||||||
|
|
||||||
|
menu.findItem(R.id.spam).setVisible(
|
||||||
|
!mMessageReference.folderName.equals(mAccount.getSpamFolderName())
|
||||||
|
&& mAccount.hasSpamFolder());
|
||||||
|
} else {
|
||||||
|
menu.findItem(R.id.copy).setVisible(false);
|
||||||
|
menu.findItem(R.id.move).setVisible(false);
|
||||||
|
menu.findItem(R.id.archive).setVisible(false);
|
||||||
|
menu.findItem(R.id.spam).setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the title of the "Toggle Unread" menu item based upon the current read state of the message.
|
||||||
|
*/
|
||||||
|
public void updateUnreadToggleTitle() {
|
||||||
|
if (mMessage != null && mMenu != null) {
|
||||||
|
if (mMessage.isSet(Flag.SEEN)) {
|
||||||
|
mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_unread_action);
|
||||||
|
} else {
|
||||||
|
mMenu.findItem(R.id.toggle_unread).setTitle(R.string.mark_as_read_action);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleActionsState(Menu menu, boolean state) {
|
||||||
|
for (int i = 0; i < menu.size(); ++i) {
|
||||||
|
menu.getItem(i).setEnabled(state);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setProgress(boolean enable) {
|
||||||
|
if (mFragmentListener != null) {
|
||||||
|
mFragmentListener.setProgress(enable);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void displayMessageSubject(String subject) {
|
||||||
|
if (mFragmentListener != null) {
|
||||||
|
mFragmentListener.displayMessageSubject(subject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void moveMessage(MessageReference reference, String destFolderName) {
|
||||||
|
mController.moveMessage(mAccount, mMessageReference.folderName, mMessage,
|
||||||
|
destFolderName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyMessage(MessageReference reference, String destFolderName) {
|
||||||
|
mController.copyMessage(mAccount, mMessageReference.folderName, mMessage,
|
||||||
|
destFolderName, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Listener extends MessagingListener {
|
||||||
|
@Override
|
||||||
|
public void loadMessageForViewHeadersAvailable(final Account account, String folder, String uid,
|
||||||
|
final Message message) {
|
||||||
|
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|
||||||
|
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clone the message object because the original could be modified by
|
||||||
|
* MessagingController later. This could lead to a ConcurrentModificationException
|
||||||
|
* when that same object is accessed by the UI thread (below).
|
||||||
|
*
|
||||||
|
* See issue 3953
|
||||||
|
*
|
||||||
|
* This is just an ugly hack to get rid of the most pressing problem. A proper way to
|
||||||
|
* fix this is to make Message thread-safe. Or, even better, rewriting the UI code to
|
||||||
|
* access messages via a ContentProvider.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
final Message clonedMessage = message.clone();
|
||||||
|
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
if (!clonedMessage.isSet(Flag.X_DOWNLOADED_FULL) &&
|
||||||
|
!clonedMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
|
||||||
|
String text = getString(R.string.message_view_downloading);
|
||||||
|
mMessageView.showStatusMessage(text);
|
||||||
|
}
|
||||||
|
mMessageView.setHeaders(clonedMessage, account);
|
||||||
|
final String subject = clonedMessage.getSubject();
|
||||||
|
if (subject == null || subject.equals("")) {
|
||||||
|
displayMessageSubject(getString(R.string.general_no_subject));
|
||||||
|
} else {
|
||||||
|
displayMessageSubject(clonedMessage.getSubject());
|
||||||
|
}
|
||||||
|
mMessageView.setOnFlagListener(new OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
onFlag();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMessageForViewBodyAvailable(final Account account, String folder,
|
||||||
|
String uid, final Message message) {
|
||||||
|
if (!mMessageReference.uid.equals(uid) ||
|
||||||
|
!mMessageReference.folderName.equals(folder) ||
|
||||||
|
!mMessageReference.accountUuid.equals(account.getUuid())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
try {
|
||||||
|
mMessage = message;
|
||||||
|
mMessageView.setMessage(account, (LocalMessage) message, mPgpData,
|
||||||
|
mController, mListener);
|
||||||
|
updateUnreadToggleTitle();
|
||||||
|
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
Log.v(K9.LOG_TAG, "loadMessageForViewBodyAvailable", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMessageForViewFailed(Account account, String folder, String uid, final Throwable t) {
|
||||||
|
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|
||||||
|
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
setProgress(false);
|
||||||
|
if (t instanceof IllegalArgumentException) {
|
||||||
|
mHandler.invalidIdError();
|
||||||
|
} else {
|
||||||
|
mHandler.networkError();
|
||||||
|
}
|
||||||
|
if (mMessage == null || mMessage.isSet(Flag.X_DOWNLOADED_PARTIAL)) {
|
||||||
|
mMessageView.showStatusMessage(getString(R.string.webview_empty_message));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMessageForViewFinished(Account account, String folder, String uid, final Message message) {
|
||||||
|
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|
||||||
|
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
setProgress(false);
|
||||||
|
mMessageView.setShowDownloadButton(message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadMessageForViewStarted(Account account, String folder, String uid) {
|
||||||
|
if (!mMessageReference.uid.equals(uid) || !mMessageReference.folderName.equals(folder)
|
||||||
|
|| !mMessageReference.accountUuid.equals(account.getUuid())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
setProgress(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAttachmentStarted(Account account, Message message, Part part, Object tag, final boolean requiresDownload) {
|
||||||
|
if (mMessage != message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
mMessageView.setAttachmentsEnabled(false);
|
||||||
|
showDialog(R.id.dialog_attachment_progress);
|
||||||
|
if (requiresDownload) {
|
||||||
|
mHandler.fetchingAttachment();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAttachmentFinished(Account account, Message message, Part part, final Object tag) {
|
||||||
|
if (mMessage != message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
mMessageView.setAttachmentsEnabled(true);
|
||||||
|
removeDialog(R.id.dialog_attachment_progress);
|
||||||
|
Object[] params = (Object[]) tag;
|
||||||
|
boolean download = (Boolean) params[0];
|
||||||
|
AttachmentView attachment = (AttachmentView) params[1];
|
||||||
|
if (download) {
|
||||||
|
attachment.writeFile();
|
||||||
|
} else {
|
||||||
|
attachment.showFile();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadAttachmentFailed(Account account, Message message, Part part, Object tag, String reason) {
|
||||||
|
if (mMessage != message) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mHandler.post(new Runnable() {
|
||||||
|
public void run() {
|
||||||
|
mMessageView.setAttachmentsEnabled(true);
|
||||||
|
removeDialog(R.id.dialog_attachment_progress);
|
||||||
|
mHandler.networkError();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// This REALLY should be in MessageCryptoView
|
||||||
|
@Override
|
||||||
|
public void onDecryptDone(PgpData pgpData) {
|
||||||
|
Account account = mAccount;
|
||||||
|
LocalMessage message = (LocalMessage) mMessage;
|
||||||
|
MessagingController controller = mController;
|
||||||
|
Listener listener = mListener;
|
||||||
|
try {
|
||||||
|
mMessageView.setMessage(account, message, pgpData, controller, listener);
|
||||||
|
} catch (MessagingException e) {
|
||||||
|
Log.e(K9.LOG_TAG, "displayMessageBody failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showDialog(int dialogId) {
|
||||||
|
DialogFragment fragment;
|
||||||
|
switch (dialogId) {
|
||||||
|
case R.id.dialog_confirm_delete: {
|
||||||
|
String title = getString(R.string.dialog_confirm_delete_title);
|
||||||
|
String message = getString(R.string.dialog_confirm_delete_message);
|
||||||
|
String confirmText = getString(R.string.dialog_confirm_delete_confirm_button);
|
||||||
|
String cancelText = getString(R.string.dialog_confirm_delete_cancel_button);
|
||||||
|
|
||||||
|
fragment = ConfirmationDialogFragment.newInstance(dialogId, title, message,
|
||||||
|
confirmText, cancelText);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case R.id.dialog_confirm_spam: {
|
||||||
|
String title = getString(R.string.dialog_confirm_spam_title);
|
||||||
|
String message = getResources().getQuantityString(R.plurals.dialog_confirm_spam_message, 1);
|
||||||
|
String confirmText = getString(R.string.dialog_confirm_spam_confirm_button);
|
||||||
|
String cancelText = getString(R.string.dialog_confirm_spam_cancel_button);
|
||||||
|
|
||||||
|
fragment = ConfirmationDialogFragment.newInstance(dialogId, title, message,
|
||||||
|
confirmText, cancelText);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case R.id.dialog_attachment_progress: {
|
||||||
|
String title = getString(R.string.dialog_attachment_progress_title);
|
||||||
|
fragment = ProgressDialogFragment.newInstance(title);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new RuntimeException("Called showDialog(int) with unknown dialog id.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fragment.setTargetFragment(this, dialogId);
|
||||||
|
fragment.show(getFragmentManager(), getDialogTag(dialogId));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void removeDialog(int dialogId) {
|
||||||
|
FragmentManager fm = getFragmentManager();
|
||||||
|
|
||||||
|
// Make sure the "show dialog" transaction has been processed when we call
|
||||||
|
// findFragmentByTag() below. Otherwise the fragment won't be found and the dialog will
|
||||||
|
// never be dismissed.
|
||||||
|
fm.executePendingTransactions();
|
||||||
|
|
||||||
|
DialogFragment fragment = (DialogFragment) fm.findFragmentByTag(getDialogTag(dialogId));
|
||||||
|
|
||||||
|
if (fragment != null) {
|
||||||
|
fragment.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDialogTag(int dialogId) {
|
||||||
|
return String.format("dialog-%d", dialogId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void zoom(KeyEvent event) {
|
||||||
|
mMessageView.zoom(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doPositiveClick(int dialogId) {
|
||||||
|
switch (dialogId) {
|
||||||
|
case R.id.dialog_confirm_delete: {
|
||||||
|
delete();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case R.id.dialog_confirm_spam: {
|
||||||
|
refileMessage(mDstFolder);
|
||||||
|
mDstFolder = null;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doNegativeClick(int dialogId) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dialogCancelled(int dialogId) {
|
||||||
|
/* do nothing */
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public interface MessageViewFragmentListener {
|
||||||
|
public void onForward(Message mMessage, PgpData mPgpData);
|
||||||
|
public void onReplyAll(Message mMessage, PgpData mPgpData);
|
||||||
|
public void onReply(Message mMessage, PgpData mPgpData);
|
||||||
|
public void displayMessageSubject(String title);
|
||||||
|
public void setProgress(boolean b);
|
||||||
|
public void restartActivity();
|
||||||
|
public void showNextMessageOrReturn();
|
||||||
|
public void messageHeaderViewAvailable(MessageHeader messageHeaderView);
|
||||||
|
}
|
||||||
|
}
|
35
src/com/fsck/k9/fragment/ProgressDialogFragment.java
Normal file
35
src/com/fsck/k9/fragment/ProgressDialogFragment.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ import android.content.ActivityNotFoundException;
|
|||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.text.InputType;
|
import android.text.InputType;
|
||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
|
|
||||||
@ -104,6 +105,38 @@ public class FileBrowserHelper {
|
|||||||
return success;
|
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) {
|
private void showPathTextInput(final Activity c, final File startPath, final FileBrowserFailOverCallback callback) {
|
||||||
AlertDialog.Builder alert = new AlertDialog.Builder(c);
|
AlertDialog.Builder alert = new AlertDialog.Builder(c);
|
||||||
|
|
||||||
|
@ -27,6 +27,7 @@ import com.fsck.k9.activity.MessageInfoHolder;
|
|||||||
import com.fsck.k9.activity.MessageList;
|
import com.fsck.k9.activity.MessageList;
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.controller.MessagingListener;
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
|
import com.fsck.k9.fragment.MessageListFragment;
|
||||||
import com.fsck.k9.helper.MessageHelper;
|
import com.fsck.k9.helper.MessageHelper;
|
||||||
import com.fsck.k9.mail.Flag;
|
import com.fsck.k9.mail.Flag;
|
||||||
import com.fsck.k9.mail.Folder;
|
import com.fsck.k9.mail.Folder;
|
||||||
@ -307,8 +308,8 @@ public class MessageProvider extends ContentProvider {
|
|||||||
final List<MessageInfoHolder> holders = queue.take();
|
final List<MessageInfoHolder> holders = queue.take();
|
||||||
|
|
||||||
// TODO add sort order parameter
|
// TODO add sort order parameter
|
||||||
Collections.sort(holders, new MessageList.ReverseComparator<MessageInfoHolder>(
|
Collections.sort(holders, new MessageListFragment.ReverseComparator<MessageInfoHolder>(
|
||||||
new MessageList.DateComparator()));
|
new MessageListFragment.DateComparator()));
|
||||||
|
|
||||||
final String[] projectionToUse;
|
final String[] projectionToUse;
|
||||||
if (projection == null) {
|
if (projection == null) {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
package com.fsck.k9.view;
|
package com.fsck.k9.view;
|
||||||
|
|
||||||
import android.app.Activity;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
@ -16,12 +16,10 @@ import com.fsck.k9.mail.Part;
|
|||||||
import com.fsck.k9.mail.internet.MimeUtility;
|
import com.fsck.k9.mail.internet.MimeUtility;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public class MessageCryptoView extends LinearLayout {
|
public class MessageCryptoView extends LinearLayout {
|
||||||
|
|
||||||
private Context mContext;
|
private Context mContext;
|
||||||
private Activity mActivity;
|
private Fragment mFragment;
|
||||||
private Button mDecryptButton;
|
private Button mDecryptButton;
|
||||||
private LinearLayout mCryptoSignatureLayout = null;
|
private LinearLayout mCryptoSignatureLayout = null;
|
||||||
private ImageView mCryptoSignatureStatusImage = null;
|
private ImageView mCryptoSignatureStatusImage = null;
|
||||||
@ -43,8 +41,8 @@ public class MessageCryptoView extends LinearLayout {
|
|||||||
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
|
mDecryptButton = (Button) findViewById(R.id.btn_decrypt);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setActivity(Activity activity) {
|
public void setFragment(Fragment fragment) {
|
||||||
mActivity = activity;
|
mFragment = fragment;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,7 +108,7 @@ public class MessageCryptoView extends LinearLayout {
|
|||||||
if (part != null) {
|
if (part != null) {
|
||||||
data = MimeUtility.getTextFromPart(part);
|
data = MimeUtility.getTextFromPart(part);
|
||||||
}
|
}
|
||||||
cryptoProvider.decrypt(mActivity, data, pgpData);
|
cryptoProvider.decrypt(mFragment, data, pgpData);
|
||||||
} catch (MessagingException me) {
|
} catch (MessagingException me) {
|
||||||
Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
|
Log.e(K9.LOG_TAG, "Unable to decrypt email.", me);
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import android.os.AsyncTask;
|
|||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
|
import android.support.v4.app.Fragment;
|
||||||
import android.util.AttributeSet;
|
import android.util.AttributeSet;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
@ -31,7 +32,7 @@ import android.widget.Toast;
|
|||||||
import com.fsck.k9.Account;
|
import com.fsck.k9.Account;
|
||||||
import com.fsck.k9.K9;
|
import com.fsck.k9.K9;
|
||||||
import com.fsck.k9.R;
|
import com.fsck.k9.R;
|
||||||
import com.fsck.k9.activity.K9Activity;
|
import com.fsck.k9.activity.K9ActivityCommon.K9ActivityMagic;
|
||||||
import com.fsck.k9.controller.MessagingController;
|
import com.fsck.k9.controller.MessagingController;
|
||||||
import com.fsck.k9.controller.MessagingListener;
|
import com.fsck.k9.controller.MessagingListener;
|
||||||
import com.fsck.k9.crypto.CryptoProvider;
|
import com.fsck.k9.crypto.CryptoProvider;
|
||||||
@ -108,7 +109,8 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
private String mText;
|
private String mText;
|
||||||
|
|
||||||
|
|
||||||
public void initialize(Activity activity) {
|
public void initialize(Fragment fragment) {
|
||||||
|
Activity activity = fragment.getActivity();
|
||||||
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
|
mMessageContentView = (MessageWebView) findViewById(R.id.message_content);
|
||||||
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
|
mAccessibleMessageContentView = (AccessibleWebView) findViewById(R.id.accessible_message_content);
|
||||||
mMessageContentView.configure();
|
mMessageContentView.configure();
|
||||||
@ -128,7 +130,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments);
|
mShowHiddenAttachments = (Button) findViewById(R.id.show_hidden_attachments);
|
||||||
mShowHiddenAttachments.setVisibility(View.GONE);
|
mShowHiddenAttachments.setVisibility(View.GONE);
|
||||||
mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt);
|
mCryptoView = (MessageCryptoView) findViewById(R.id.layout_decrypt);
|
||||||
mCryptoView.setActivity(activity);
|
mCryptoView.setFragment(fragment);
|
||||||
mCryptoView.setupChildViews();
|
mCryptoView.setupChildViews();
|
||||||
mShowPicturesAction = findViewById(R.id.show_pictures);
|
mShowPicturesAction = findViewById(R.id.show_pictures);
|
||||||
mShowMessageAction = findViewById(R.id.show_message);
|
mShowMessageAction = findViewById(R.id.show_message);
|
||||||
@ -157,7 +159,7 @@ public class SingleMessageView extends LinearLayout implements OnClickListener,
|
|||||||
mHeaderPlaceHolder.removeView(mHeaderContainer);
|
mHeaderPlaceHolder.removeView(mHeaderContainer);
|
||||||
// the HTC version of WebView tries to force the background of the
|
// the HTC version of WebView tries to force the background of the
|
||||||
// titlebar, which is really unfair.
|
// titlebar, which is really unfair.
|
||||||
mHeaderContainer.setBackgroundColor(((K9Activity)activity).getThemeBackgroundColor());
|
mHeaderContainer.setBackgroundColor(((K9ActivityMagic)activity).getThemeBackgroundColor());
|
||||||
|
|
||||||
mTitleBarHeaderContainer = new LinearLayout(activity);
|
mTitleBarHeaderContainer = new LinearLayout(activity);
|
||||||
mMessageContentView.setEmbeddedTitleBarCompat(mTitleBarHeaderContainer);
|
mMessageContentView.setEmbeddedTitleBarCompat(mTitleBarHeaderContainer);
|
||||||
|
Loading…
Reference in New Issue
Block a user