diff --git a/app/src/main/java/eu/faircode/email/ActivityCompose.java b/app/src/main/java/eu/faircode/email/ActivityCompose.java index c022685d36..dc8e43aec7 100644 --- a/app/src/main/java/eu/faircode/email/ActivityCompose.java +++ b/app/src/main/java/eu/faircode/email/ActivityCompose.java @@ -19,6 +19,8 @@ package eu.faircode.email; Copyright 2018-2021 by Marcel Bokhorst (M66B) */ +import android.app.NotificationManager; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -30,12 +32,17 @@ import androidx.core.app.TaskStackBuilder; import androidx.core.net.MailTo; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.lifecycle.LifecycleOwner; +import java.io.File; import java.util.ArrayList; +import java.util.Date; +import java.util.List; import java.util.Map; public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener { static final int PI_REPLY = 1; + static final long UNDO_DELAY = 5000; // milliseconds @Override protected void onCreate(Bundle savedInstanceState) { @@ -204,4 +211,92 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB Intent.ACTION_SEND.equals(action) || Intent.ACTION_SEND_MULTIPLE.equals(action)); } + + static void undoSend(long id, final Context context, final LifecycleOwner owner, final FragmentManager manager) { + Bundle args = new Bundle(); + args.putLong("id", id); + + new SimpleTask() { + @Override + protected EntityMessage onExecute(Context context, Bundle args) { + long id = args.getLong("id"); + + DB db = DB.getInstance(context); + + EntityOperation operation = db.operation().getOperation(id, EntityOperation.SEND); + if (operation != null) + if ("executing".equals(operation.state)) { + // Trigger update + db.message().setMessageUiBusy(id, new Date().getTime()); + return null; + } else + db.operation().deleteOperation(operation.id); + + EntityMessage message; + + try { + db.beginTransaction(); + + message = db.message().getMessage(id); + if (message == null) + return null; + + db.folder().setFolderError(message.folder, null); + if (message.identity != null) + db.identity().setIdentityError(message.identity, null); + + File source = message.getFile(context); + + // Insert into drafts + EntityFolder drafts = db.folder().getFolderByType(message.account, EntityFolder.DRAFTS); + if (drafts == null) + throw new IllegalArgumentException(context.getString(R.string.title_no_drafts)); + + message.id = null; + message.folder = drafts.id; + message.fts = false; + message.ui_snoozed = null; + message.error = null; + message.id = db.message().insertMessage(message); + + File target = message.getFile(context); + source.renameTo(target); + + List attachments = db.attachment().getAttachments(id); + for (EntityAttachment attachment : attachments) + db.attachment().setMessage(attachment.id, message.id); + + EntityOperation.queue(context, message, EntityOperation.ADD); + + // Delete from outbox + db.message().deleteMessage(id); // will delete operation too + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + ServiceSynchronize.eval(context, "outbox/drafts"); + + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.cancel("send:" + id, 1); + + return message; + } + + @Override + protected void onExecuted(Bundle args, EntityMessage draft) { + if (draft != null) + context.startActivity( + new Intent(context, ActivityCompose.class) + .putExtra("action", "edit") + .putExtra("id", draft.id)); + } + + @Override + protected void onException(Bundle args, Throwable ex) { + Log.unexpectedError(manager, ex, !(ex instanceof IllegalArgumentException)); + } + }.execute(context, owner, args, "undo:sent"); + } } diff --git a/app/src/main/java/eu/faircode/email/ActivityView.java b/app/src/main/java/eu/faircode/email/ActivityView.java index 309dff17a1..920ec676df 100644 --- a/app/src/main/java/eu/faircode/email/ActivityView.java +++ b/app/src/main/java/eu/faircode/email/ActivityView.java @@ -136,6 +136,7 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB static final String ACTION_EDIT_RULES = BuildConfig.APPLICATION_ID + ".EDIT_RULES"; static final String ACTION_EDIT_RULE = BuildConfig.APPLICATION_ID + ".EDIT_RULE"; static final String ACTION_NEW_MESSAGE = BuildConfig.APPLICATION_ID + ".NEW_MESSAGE"; + static final String ACTION_SENT_UNDO = BuildConfig.APPLICATION_ID + ".SENT_UNDO"; private static final int UPDATE_TIMEOUT = 15 * 1000; // milliseconds private static final long EXIT_DELAY = 2500L; // milliseconds @@ -156,7 +157,10 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB } LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(this); - lbm.registerReceiver(creceiver, new IntentFilter(ACTION_NEW_MESSAGE)); + IntentFilter iff = new IntentFilter(); + iff.addAction(ACTION_NEW_MESSAGE); + iff.addAction(ACTION_SENT_UNDO); + lbm.registerReceiver(creceiver, iff); if (savedInstanceState != null) searching = savedInstanceState.getBoolean("fair:searching"); @@ -741,18 +745,29 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB return super.onOptionsItemSelected(item); } - public void undo(String title, final Bundle args, final SimpleTask move, final SimpleTask show) { + public void undo(String title, Runnable move, Runnable show) { SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); int undo_timeout = prefs.getInt("undo_timeout", 5000); - if (undo_timeout == 0) { - move.execute(this, args, "undo:move"); - return; - } + if (undo_timeout == 0) + try { + move.run(); + } catch (Throwable ex) { + Log.e(ex); + } + else + undo(undo_timeout, title, move, show); + } + public void undo(long undo_timeout, String title, final Runnable move, final Runnable show) { if (drawerLayout == null || drawerLayout.getChildCount() == 0) { Log.e("Undo: drawer missing"); - show.execute(this, args, "undo:move"); + if (show != null) + try { + show.run(); + } catch (Throwable ex) { + Log.e(ex); + } return; } @@ -768,7 +783,12 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB public void run() { Log.i("Undo timeout"); snackbar.dismiss(); - move.execute(ActivityView.this, args, "undo:move"); + if (move != null) + try { + move.run(); + } catch (Throwable ex) { + Log.e(ex); + } } }; @@ -778,7 +798,12 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB Log.i("Undo cancel"); getMainHandler().removeCallbacks(timeout); snackbar.dismiss(); - show.execute(ActivityView.this, args, "undo:show"); + if (show != null) + try { + show.run(); + } catch (Throwable ex) { + Log.e(ex); + } } }); @@ -1273,7 +1298,11 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB private BroadcastReceiver creceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { - onNewMessage(intent); + String action = intent.getAction(); + if (ACTION_NEW_MESSAGE.equals(action)) + onNewMessage(intent); + else if (ACTION_SENT_UNDO.equals(action)) + onSentUndo(intent); } }; @@ -1296,6 +1325,16 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB updatedFolders.add(-1L); } + private void onSentUndo(Intent intent) { + undo(ActivityCompose.UNDO_DELAY, getString(R.string.title_sending), null, new Runnable() { + @Override + public void run() { + long id = intent.getLongExtra("id", -1); + ActivityCompose.undoSend(id, ActivityView.this, ActivityView.this, getSupportFragmentManager()); + } + }); + } + private BroadcastReceiver receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { diff --git a/app/src/main/java/eu/faircode/email/AdapterMessage.java b/app/src/main/java/eu/faircode/email/AdapterMessage.java index a09c6ca559..708a127fd9 100644 --- a/app/src/main/java/eu/faircode/email/AdapterMessage.java +++ b/app/src/main/java/eu/faircode/email/AdapterMessage.java @@ -3054,7 +3054,7 @@ public class AdapterMessage extends RecyclerView.Adapter new Date().getTime()) { + Intent undo = new Intent(ActivityView.ACTION_SENT_UNDO); + undo.putExtra("id", draft.id); + + LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); + lbm.sendBroadcast(undo); + } } return draft; diff --git a/app/src/main/java/eu/faircode/email/FragmentMessages.java b/app/src/main/java/eu/faircode/email/FragmentMessages.java index 5b3f46eae9..dd9d1bc1a0 100644 --- a/app/src/main/java/eu/faircode/email/FragmentMessages.java +++ b/app/src/main/java/eu/faircode/email/FragmentMessages.java @@ -2035,7 +2035,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } if (EntityFolder.OUTBOX.equals(message.folderType)) { - onActionUndoSend(message, getContext(), getViewLifecycleOwner(), getParentFragmentManager()); + ActivityCompose.undoSend(message.id, getContext(), getViewLifecycleOwner(), getParentFragmentManager()); return; } @@ -3624,94 +3624,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. }.execute(FragmentMessages.this, args, "messages:delete"); } - static void onActionUndoSend(TupleMessageEx message, final Context context, final LifecycleOwner owner, final FragmentManager manager) { - Bundle args = new Bundle(); - args.putLong("id", message.id); - - new SimpleTask() { - @Override - protected EntityMessage onExecute(Context context, Bundle args) { - long id = args.getLong("id"); - - DB db = DB.getInstance(context); - - EntityOperation operation = db.operation().getOperation(message.id, EntityOperation.SEND); - if (operation != null) - if ("executing".equals(operation.state)) { - // Trigger update - db.message().setMessageUiBusy(message.id, new Date().getTime()); - return null; - } else - db.operation().deleteOperation(operation.id); - - EntityMessage message; - - try { - db.beginTransaction(); - - message = db.message().getMessage(id); - if (message == null) - return null; - - db.folder().setFolderError(message.folder, null); - if (message.identity != null) - db.identity().setIdentityError(message.identity, null); - - File source = message.getFile(context); - - // Insert into drafts - EntityFolder drafts = db.folder().getFolderByType(message.account, EntityFolder.DRAFTS); - if (drafts == null) - throw new IllegalArgumentException(context.getString(R.string.title_no_drafts)); - - message.id = null; - message.folder = drafts.id; - message.fts = false; - message.ui_snoozed = null; - message.error = null; - message.id = db.message().insertMessage(message); - - File target = message.getFile(context); - source.renameTo(target); - - List attachments = db.attachment().getAttachments(id); - for (EntityAttachment attachment : attachments) - db.attachment().setMessage(attachment.id, message.id); - - EntityOperation.queue(context, message, EntityOperation.ADD); - - // Delete from outbox - db.message().deleteMessage(id); // will delete operation too - - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } - - ServiceSynchronize.eval(context, "outbox/drafts"); - - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.cancel("send:" + id, 1); - - return message; - } - - @Override - protected void onExecuted(Bundle args, EntityMessage draft) { - if (draft != null) - context.startActivity( - new Intent(context, ActivityCompose.class) - .putExtra("action", "edit") - .putExtra("id", draft.id)); - } - - @Override - protected void onException(Bundle args, Throwable ex) { - Log.unexpectedError(manager, ex, !(ex instanceof IllegalArgumentException)); - } - }.execute(context, owner, args, "message:move:draft"); - } - @Override public void onSaveInstanceState(Bundle outState) { outState.putBoolean("fair:reset", reset); @@ -5481,80 +5393,88 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences. } } - SimpleTask move = new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - ArrayList result = args.getParcelableArrayList("result"); - - DB db = DB.getInstance(context); - try { - db.beginTransaction(); - - for (MessageTarget target : result) { - EntityMessage message = db.message().getMessage(target.id); - if (message == null || !message.ui_hide) - continue; + FragmentActivity activity = getActivity(); + if (!(activity instanceof ActivityView)) { + Log.e("Undo: activity missing"); + return; + } - Log.i("Move id=" + target.id + " target=" + target.targetFolder.name); - db.message().setMessageUiBusy(target.id, null); - db.message().setMessageLastAttempt(target.id, new Date().getTime()); - EntityOperation.queue(context, message, EntityOperation.MOVE, target.targetFolder.id); - } + String title = getString(R.string.title_move_undo, getDisplay(result, true), result.size()); + ((ActivityView) activity).undo(title, + new Runnable() { + @Override + public void run() { + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + ArrayList result = args.getParcelableArrayList("result"); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - ServiceSynchronize.eval(context, "move"); + for (MessageTarget target : result) { + EntityMessage message = db.message().getMessage(target.id); + if (message == null || !message.ui_hide) + continue; - return null; - } + Log.i("Move id=" + target.id + " target=" + target.targetFolder.name); + db.message().setMessageUiBusy(target.id, null); + db.message().setMessageLastAttempt(target.id, new Date().getTime()); + EntityOperation.queue(context, message, EntityOperation.MOVE, target.targetFolder.id); + } - @Override - protected void onException(Bundle args, Throwable ex) { - Log.e(ex); - } - }; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - SimpleTask show = new SimpleTask() { - @Override - protected Void onExecute(Context context, Bundle args) { - ArrayList result = args.getParcelableArrayList("result"); + ServiceSynchronize.eval(context, "move"); - DB db = DB.getInstance(context); - try { - db.beginTransaction(); + return null; + } - for (MessageTarget target : result) { - Log.i("Move undo id=" + target.id); - db.message().setMessageUiBusy(target.id, null); - db.message().setMessageUiHide(target.id, false); - db.message().setMessageLastAttempt(target.id, new Date().getTime()); + @Override + protected void onException(Bundle args, Throwable ex) { + Log.e(ex); + } + }.execute(FragmentMessages.this, args, "undo:move"); } + }, + new Runnable() { + @Override + public void run() { + new SimpleTask() { + @Override + protected Void onExecute(Context context, Bundle args) { + ArrayList result = args.getParcelableArrayList("result"); - db.setTransactionSuccessful(); - } finally { - db.endTransaction(); - } + DB db = DB.getInstance(context); + try { + db.beginTransaction(); - return null; - } + for (MessageTarget target : result) { + Log.i("Move undo id=" + target.id); + db.message().setMessageUiBusy(target.id, null); + db.message().setMessageUiHide(target.id, false); + db.message().setMessageLastAttempt(target.id, new Date().getTime()); + } - @Override - protected void onException(Bundle args, Throwable ex) { - Log.e(ex); - } - }; + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } - FragmentActivity activity = getActivity(); - if (!(activity instanceof ActivityView)) { - Log.e("Undo: activity missing"); - return; - } + return null; + } - String title = getString(R.string.title_move_undo, getDisplay(result, true), result.size()); - ((ActivityView) activity).undo(title, args, move, show); + @Override + protected void onException(Bundle args, Throwable ex) { + Log.e(ex); + } + }.execute(FragmentMessages.this, args, "undo:show"); + } + }); } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 33975502f0..caee132c77 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1003,6 +1003,7 @@ Discard Save Send + Sending … Send now Send via Send at …