Added undo send

pull/196/head
M66B 5 years ago
parent 75ac1afcfe
commit da06871d98

@ -19,6 +19,8 @@ package eu.faircode.email;
Copyright 2018-2021 by Marcel Bokhorst (M66B) Copyright 2018-2021 by Marcel Bokhorst (M66B)
*/ */
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
@ -30,12 +32,17 @@ import androidx.core.app.TaskStackBuilder;
import androidx.core.net.MailTo; import androidx.core.net.MailTo;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.LifecycleOwner;
import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map; import java.util.Map;
public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener { public class ActivityCompose extends ActivityBase implements FragmentManager.OnBackStackChangedListener {
static final int PI_REPLY = 1; static final int PI_REPLY = 1;
static final long UNDO_DELAY = 5000; // milliseconds
@Override @Override
protected void onCreate(Bundle savedInstanceState) { 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.equals(action) ||
Intent.ACTION_SEND_MULTIPLE.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<EntityMessage>() {
@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<EntityAttachment> 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");
}
} }

@ -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_RULES = BuildConfig.APPLICATION_ID + ".EDIT_RULES";
static final String ACTION_EDIT_RULE = BuildConfig.APPLICATION_ID + ".EDIT_RULE"; 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_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 int UPDATE_TIMEOUT = 15 * 1000; // milliseconds
private static final long EXIT_DELAY = 2500L; // 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); 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) if (savedInstanceState != null)
searching = savedInstanceState.getBoolean("fair:searching"); searching = savedInstanceState.getBoolean("fair:searching");
@ -741,18 +745,29 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
public void undo(String title, final Bundle args, final SimpleTask<Void> move, final SimpleTask<Void> show) { public void undo(String title, Runnable move, Runnable show) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int undo_timeout = prefs.getInt("undo_timeout", 5000); int undo_timeout = prefs.getInt("undo_timeout", 5000);
if (undo_timeout == 0) { if (undo_timeout == 0)
move.execute(this, args, "undo:move"); try {
return; 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) { if (drawerLayout == null || drawerLayout.getChildCount() == 0) {
Log.e("Undo: drawer missing"); Log.e("Undo: drawer missing");
show.execute(this, args, "undo:move"); if (show != null)
try {
show.run();
} catch (Throwable ex) {
Log.e(ex);
}
return; return;
} }
@ -768,7 +783,12 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
public void run() { public void run() {
Log.i("Undo timeout"); Log.i("Undo timeout");
snackbar.dismiss(); 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"); Log.i("Undo cancel");
getMainHandler().removeCallbacks(timeout); getMainHandler().removeCallbacks(timeout);
snackbar.dismiss(); 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() { private BroadcastReceiver creceiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (ACTION_NEW_MESSAGE.equals(action))
onNewMessage(intent); 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); 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() { private BroadcastReceiver receiver = new BroadcastReceiver() {
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {

@ -3054,7 +3054,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} else if (id == R.id.ibVerify) { } else if (id == R.id.ibVerify) {
onActionDecrypt(message, false); onActionDecrypt(message, false);
} else if (id == R.id.ibUndo) { } else if (id == R.id.ibUndo) {
FragmentMessages.onActionUndoSend(message, context, owner, parentFragment.getParentFragmentManager()); ActivityCompose.undoSend(message.id, context, owner, parentFragment.getParentFragmentManager());
} else if (id == R.id.ibRule) { } else if (id == R.id.ibRule) {
onMenuCreateRule(message); onMenuCreateRule(message);
} else if (id == R.id.ibUnsubscribe) { } else if (id == R.id.ibUnsubscribe) {

@ -107,6 +107,7 @@ import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.Lifecycle; import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.Observer; import androidx.lifecycle.Observer;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.preference.PreferenceManager; import androidx.preference.PreferenceManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
@ -5017,6 +5018,14 @@ public class FragmentCompose extends FragmentBase {
else { else {
Log.i("Delayed send id=" + draft.id + " at " + new Date(draft.ui_snoozed)); Log.i("Delayed send id=" + draft.id + " at " + new Date(draft.ui_snoozed));
EntityMessage.snooze(context, draft.id, draft.ui_snoozed); EntityMessage.snooze(context, draft.id, draft.ui_snoozed);
if (draft.ui_snoozed - 2 * ActivityCompose.UNDO_DELAY > 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; return draft;

@ -2035,7 +2035,7 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} }
if (EntityFolder.OUTBOX.equals(message.folderType)) { if (EntityFolder.OUTBOX.equals(message.folderType)) {
onActionUndoSend(message, getContext(), getViewLifecycleOwner(), getParentFragmentManager()); ActivityCompose.undoSend(message.id, getContext(), getViewLifecycleOwner(), getParentFragmentManager());
return; return;
} }
@ -3624,94 +3624,6 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
}.execute(FragmentMessages.this, args, "messages:delete"); }.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<EntityMessage>() {
@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<EntityAttachment> 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 @Override
public void onSaveInstanceState(Bundle outState) { public void onSaveInstanceState(Bundle outState) {
outState.putBoolean("fair:reset", reset); outState.putBoolean("fair:reset", reset);
@ -5481,7 +5393,18 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
} }
} }
SimpleTask<Void> move = new SimpleTask<Void>() { FragmentActivity activity = getActivity();
if (!(activity instanceof ActivityView)) {
Log.e("Undo: activity missing");
return;
}
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<Void>() {
@Override @Override
protected Void onExecute(Context context, Bundle args) { protected Void onExecute(Context context, Bundle args) {
ArrayList<MessageTarget> result = args.getParcelableArrayList("result"); ArrayList<MessageTarget> result = args.getParcelableArrayList("result");
@ -5515,9 +5438,13 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
Log.e(ex); Log.e(ex);
} }
}; }.execute(FragmentMessages.this, args, "undo:move");
}
SimpleTask<Void> show = new SimpleTask<Void>() { },
new Runnable() {
@Override
public void run() {
new SimpleTask<Void>() {
@Override @Override
protected Void onExecute(Context context, Bundle args) { protected Void onExecute(Context context, Bundle args) {
ArrayList<MessageTarget> result = args.getParcelableArrayList("result"); ArrayList<MessageTarget> result = args.getParcelableArrayList("result");
@ -5545,16 +5472,9 @@ public class FragmentMessages extends FragmentBase implements SharedPreferences.
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
Log.e(ex); Log.e(ex);
} }
}; }.execute(FragmentMessages.this, args, "undo:show");
FragmentActivity activity = getActivity();
if (!(activity instanceof ActivityView)) {
Log.e("Undo: activity missing");
return;
} }
});
String title = getString(R.string.title_move_undo, getDisplay(result, true), result.size());
((ActivityView) activity).undo(title, args, move, show);
} }
@Override @Override

@ -1003,6 +1003,7 @@
<string name="title_discard">Discard</string> <string name="title_discard">Discard</string>
<string name="title_save">Save</string> <string name="title_save">Save</string>
<string name="title_send">Send</string> <string name="title_send">Send</string>
<string name="title_sending">Sending &#8230;</string>
<string name="title_send_now">Send now</string> <string name="title_send_now">Send now</string>
<string name="title_send_via">Send via</string> <string name="title_send_via">Send via</string>
<string name="title_send_at">Send at &#8230;</string> <string name="title_send_at">Send at &#8230;</string>

Loading…
Cancel
Save