Added delayed send

pull/147/head
M66B 6 years ago
parent ceb9c50896
commit 4401d134e9

@ -41,7 +41,6 @@ None at this moment.
* Resize images: this is not a feature directly related to email and there are plenty of apps that can do this for you. * Resize images: this is not a feature directly related to email and there are plenty of apps that can do this for you.
* Calendar events: opening the attached calendar file should open the related calendar app. * Calendar events: opening the attached calendar file should open the related calendar app.
* Executing filter rules: filter rules should be executed on the server because a battery powered device with possibly an unstable internet connection is not suitable for this. * Executing filter rules: filter rules should be executed on the server because a battery powered device with possibly an unstable internet connection is not suitable for this.
* Send timer: basically the same as executing filter rules. Delayed sending is not supported by [IMAP](https://en.wikipedia.org/wiki/Internet_Message_Access_Protocol). You could move messages to a "to do" folder instead.
* Badge count: there is no standard Android API for this and third party solutions might stop working anytime. For example *ShortcutBadger* [has lots of problems](https://github.com/leolin310148/ShortcutBadger/issues). You can use the provided widget instead. * Badge count: there is no standard Android API for this and third party solutions might stop working anytime. For example *ShortcutBadger* [has lots of problems](https://github.com/leolin310148/ShortcutBadger/issues). You can use the provided widget instead.
* Switch language: although it is possible to change the language of an app, Android is not designed for this. Better fix the translation in your language if needed, see [this FAQ](#user-content-faq26) about how to. * Switch language: although it is possible to change the language of an app, Android is not designed for this. Better fix the translation in your language if needed, see [this FAQ](#user-content-faq26) about how to.
* Select identities to show in unified inbox: this would add complexity for something which would hardly be used. * Select identities to show in unified inbox: this would add complexity for something which would hardly be used.

@ -37,7 +37,8 @@ This app starts a foreground service with a low priority status bar notification
* Account/identity colors * Account/identity colors
* Notifications per account * Notifications per account
* Notifications with message preview (requires Android 7 Nougat or later) * Notifications with message preview (requires Android 7 Nougat or later)
* Snoozing messages * Snooze messages
* Send messages after selected time
* Reply templates * Reply templates
* Search on server * Search on server
* Keyword management * Keyword management

@ -702,7 +702,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
bnvActions.getMenu().findItem(R.id.action_delete).setVisible( bnvActions.getMenu().findItem(R.id.action_delete).setVisible(
(inTrash && message.msgid != null) || (inTrash && message.msgid != null) ||
(!inTrash && hasTrash && message.uid != null) || (!inTrash && hasTrash && message.uid != null) ||
(inOutbox && !TextUtils.isEmpty(message.error))); (inOutbox && (message.ui_snoozed != null || !TextUtils.isEmpty(message.error))));
bnvActions.getMenu().findItem(R.id.action_delete).setTitle(inTrash ? R.string.title_delete : R.string.title_trash); bnvActions.getMenu().findItem(R.id.action_delete).setTitle(inTrash ? R.string.title_delete : R.string.title_trash);
bnvActions.getMenu().findItem(R.id.action_move).setVisible(message.uid != null); bnvActions.getMenu().findItem(R.id.action_move).setVisible(message.uid != null);
bnvActions.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive); bnvActions.getMenu().findItem(R.id.action_archive).setVisible(message.uid != null && !inArchive && hasArchive);
@ -774,6 +774,53 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
} }
} }
private void onShowSnoozed(TupleMessageEx message) {
if (message.ui_snoozed != null)
Toast.makeText(context, new Date(message.ui_snoozed).toString(), Toast.LENGTH_LONG).show();
}
private void onToggleFlag(TupleMessageEx message) {
Bundle args = new Bundle();
args.putLong("id", message.id);
args.putBoolean("flagged", !message.ui_flagged);
args.putBoolean("thread", viewType != ViewType.THREAD);
Log.i("Set message id=" + message.id + " flagged=" + !message.ui_flagged);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean flagged = args.getBoolean("flagged");
boolean thread = args.getBoolean("thread");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading && thread ? null : id, message.folder);
for (EntityMessage threaded : messages)
EntityOperation.queue(context, db, threaded, EntityOperation.FLAG, flagged);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:flag");
}
private void onAddContact(TupleMessageEx message) { private void onAddContact(TupleMessageEx message) {
for (Address address : message.from) { for (Address address : message.from) {
InternetAddress ia = (InternetAddress) address; InternetAddress ia = (InternetAddress) address;
@ -1139,26 +1186,26 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.action_more: case R.id.action_more:
onMore(data); onActionMore(data);
return true; return true;
case R.id.action_delete: case R.id.action_delete:
onDelete(data); onActionDelete(data);
return true; return true;
case R.id.action_move: case R.id.action_move:
onMove(data); onActionMove(data);
return true; return true;
case R.id.action_archive: case R.id.action_archive:
onArchive(data); onActionArchive(data);
return true; return true;
case R.id.action_reply: case R.id.action_reply:
onReply(data, false); onActionReply(data, false);
return true; return true;
default: default:
return false; return false;
} }
} }
private void onJunk(final ActionData data) { private void onMenuJunk(final ActionData data) {
new DialogBuilderLifecycle(context, owner) new DialogBuilderLifecycle(context, owner)
.setMessage(R.string.title_ask_spam) .setMessage(R.string.title_ask_spam)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@ -1202,7 +1249,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
.show(); .show();
} }
private void onForward(final ActionData data, final boolean raw) { private void onMenuForward(final ActionData data, final boolean raw) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); args.putLong("id", data.message.id);
@ -1245,7 +1292,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:forward"); }.execute(context, owner, args, "message:forward");
} }
private void onAnswer(final ActionData data) { private void onMenuAnswer(final ActionData data) {
new SimpleTask<List<EntityAnswer>>() { new SimpleTask<List<EntityAnswer>>() {
@Override @Override
protected List<EntityAnswer> onExecute(Context context, Bundle args) { protected List<EntityAnswer> onExecute(Context context, Bundle args) {
@ -1313,7 +1360,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, new Bundle(), "message:answer"); }.execute(context, owner, new Bundle(), "message:answer");
} }
private void onUnseen(final ActionData data) { private void onMenuUnseen(final ActionData data) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); args.putLong("id", data.message.id);
@ -1353,54 +1400,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:unseen"); }.execute(context, owner, args, "message:unseen");
} }
private void onShowSnoozed(TupleMessageEx message) { private void onMenuShare(ActionData data) {
if (message.ui_snoozed != null)
Toast.makeText(context, new Date(message.ui_snoozed).toString(), Toast.LENGTH_LONG).show();
}
private void onToggleFlag(TupleMessageEx message) {
Bundle args = new Bundle();
args.putLong("id", message.id);
args.putBoolean("flagged", !message.ui_flagged);
args.putBoolean("thread", viewType != ViewType.THREAD);
Log.i("Set message id=" + message.id + " flagged=" + !message.ui_flagged);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
boolean flagged = args.getBoolean("flagged");
boolean thread = args.getBoolean("thread");
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityMessage message = db.message().getMessage(id);
if (message == null)
return null;
List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading && thread ? null : id, message.folder);
for (EntityMessage threaded : messages)
EntityOperation.queue(context, db, threaded, EntityOperation.FLAG, flagged);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return null;
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:flag");
}
private void onShare(ActionData data) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); args.putLong("id", data.message.id);
@ -1452,7 +1452,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:share"); }.execute(context, owner, args, "message:share");
} }
private void onShowHeaders(ActionData data) { private void onMenuShowHeaders(ActionData data) {
boolean show_headers = !properties.getValue("headers", data.message.id); boolean show_headers = !properties.getValue("headers", data.message.id);
properties.setValue("headers", data.message.id, show_headers); properties.setValue("headers", data.message.id, show_headers);
if (show_headers && data.message.headers == null) { if (show_headers && data.message.headers == null) {
@ -1496,7 +1496,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
notifyDataSetChanged(); notifyDataSetChanged();
} }
private void onManageKeywords(ActionData data) { private void onMenuManageKeywords(ActionData data) {
if (!Helper.isPro(context)) { if (!Helper.isPro(context)) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO)); lbm.sendBroadcast(new Intent(ActivityView.ACTION_SHOW_PRO));
@ -1627,14 +1627,14 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:keywords"); }.execute(context, owner, args, "message:keywords");
} }
private void onDecrypt(ActionData data) { private void onMenuDecrypt(ActionData data) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context); LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast( lbm.sendBroadcast(
new Intent(ActivityView.ACTION_DECRYPT) new Intent(ActivityView.ACTION_DECRYPT)
.putExtra("id", data.message.id)); .putExtra("id", data.message.id));
} }
private void onMore(final ActionData data) { private void onActionMore(final ActionData data) {
boolean show_headers = properties.getValue("headers", data.message.id); boolean show_headers = properties.getValue("headers", data.message.id);
View anchor = bnvActions.findViewById(R.id.action_more); View anchor = bnvActions.findViewById(R.id.action_more);
@ -1668,34 +1668,34 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
public boolean onMenuItemClick(MenuItem target) { public boolean onMenuItemClick(MenuItem target) {
switch (target.getItemId()) { switch (target.getItemId()) {
case R.id.menu_junk: case R.id.menu_junk:
onJunk(data); onMenuJunk(data);
return true; return true;
case R.id.menu_forward: case R.id.menu_forward:
onForward(data, false); onMenuForward(data, false);
return true; return true;
case R.id.menu_forward_raw: case R.id.menu_forward_raw:
onForward(data, true); onMenuForward(data, true);
return true; return true;
case R.id.menu_reply_all: case R.id.menu_reply_all:
onReply(data, true); onActionReply(data, true);
return true; return true;
case R.id.menu_answer: case R.id.menu_answer:
onAnswer(data); onMenuAnswer(data);
return true; return true;
case R.id.menu_unseen: case R.id.menu_unseen:
onUnseen(data); onMenuUnseen(data);
return true; return true;
case R.id.menu_share: case R.id.menu_share:
onShare(data); onMenuShare(data);
return true; return true;
case R.id.menu_show_headers: case R.id.menu_show_headers:
onShowHeaders(data); onMenuShowHeaders(data);
return true; return true;
case R.id.menu_manage_keywords: case R.id.menu_manage_keywords:
onManageKeywords(data); onMenuManageKeywords(data);
return true; return true;
case R.id.menu_decrypt: case R.id.menu_decrypt:
onDecrypt(data); onMenuDecrypt(data);
return true; return true;
default: default:
return false; return false;
@ -1705,7 +1705,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
popupMenu.show(); popupMenu.show();
} }
private void onDelete(final ActionData data) { private void onActionDelete(final ActionData data) {
if (data.delete) { if (data.delete) {
// No trash or is trash // No trash or is trash
new DialogBuilderLifecycle(context, owner) new DialogBuilderLifecycle(context, owner)
@ -1729,8 +1729,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (message == null) if (message == null)
return null; return null;
if (message.uid == null && !TextUtils.isEmpty(message.error)) { EntityFolder folder = db.folder().getFolder(message.folder);
// outbox
if (EntityFolder.OUTBOX.equals(folder.type) &&
(message.ui_snoozed != null || !TextUtils.isEmpty(message.error))) {
db.message().deleteMessage(id); db.message().deleteMessage(id);
db.folder().setFolderError(message.folder, null); db.folder().setFolderError(message.folder, null);
@ -1762,7 +1764,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
properties.move(data.message.id, EntityFolder.TRASH, true); properties.move(data.message.id, EntityFolder.TRASH, true);
} }
private void onMove(ActionData data) { private void onActionMove(ActionData data) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); args.putLong("id", data.message.id);
@ -1848,11 +1850,11 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
}.execute(context, owner, args, "message:move:list"); }.execute(context, owner, args, "message:move:list");
} }
private void onArchive(ActionData data) { private void onActionArchive(ActionData data) {
properties.move(data.message.id, EntityFolder.ARCHIVE, true); properties.move(data.message.id, EntityFolder.ARCHIVE, true);
} }
private void onReply(final ActionData data, final boolean all) { private void onActionReply(final ActionData data, final boolean all) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("id", data.message.id); args.putLong("id", data.message.id);

@ -103,7 +103,7 @@ public interface DaoMessage {
" JOIN folder f ON f.id = :folder" + " JOIN folder f ON f.id = :folder" +
" WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" + " WHERE (message.account = f.account OR folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND (NOT message.ui_hide OR :debug)" + " AND (NOT message.ui_hide OR :debug)" +
" AND (:snoozed OR :found OR ui_snoozed IS NULL)" + " AND (:snoozed OR :found OR ui_snoozed IS NULL OR folder.type = '" + EntityFolder.OUTBOX + "')" +
" AND (NOT :found OR ui_found = :found)" + " AND (NOT :found OR ui_found = :found)" +
" GROUP BY CASE WHEN message.thread IS NULL OR NOT :threading THEN message.id ELSE message.thread END" + " GROUP BY CASE WHEN message.thread IS NULL OR NOT :threading THEN message.id ELSE message.thread END" +
" HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" + " HAVING SUM(CASE WHEN folder.id = :folder THEN 1 ELSE 0 END) > 0" +

@ -101,7 +101,7 @@ public class EntityMessage implements Serializable {
public String extra; // plus public String extra; // plus
public Long replying; public Long replying;
public Long forwarding; public Long forwarding;
public Long uid; // compose = null public Long uid; // compose/moved = null
public String msgid; public String msgid;
public String references; public String references;
public String deliveredto; public String deliveredto;

@ -72,6 +72,7 @@ import android.widget.EditText;
import android.widget.FilterQueryProvider; import android.widget.FilterQueryProvider;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.MultiAutoCompleteTextView; import android.widget.MultiAutoCompleteTextView;
import android.widget.NumberPicker;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
@ -288,8 +289,9 @@ public class FragmentCompose extends FragmentEx {
switch (action) { switch (action) {
case R.id.action_delete: case R.id.action_delete:
onDelete(); onActionDelete();
break; break;
case R.id.action_send: case R.id.action_send:
final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext()); final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(getContext());
boolean autosend = prefs.getBoolean("autosend", false); boolean autosend = prefs.getBoolean("autosend", false);
@ -327,6 +329,7 @@ public class FragmentCompose extends FragmentEx {
onAction(action); onAction(action);
} }
break; break;
default: default:
onAction(action); onAction(action);
} }
@ -496,7 +499,7 @@ public class FragmentCompose extends FragmentEx {
args.putString("subject", getArguments().getString("subject")); args.putString("subject", getArguments().getString("subject"));
args.putString("body", getArguments().getString("body")); args.putString("body", getArguments().getString("body"));
args.putParcelableArrayList("attachments", getArguments().getParcelableArrayList("attachments")); args.putParcelableArrayList("attachments", getArguments().getParcelableArrayList("attachments"));
draftLoader.execute(this, args, "draft:new"); draftLoader.execute(this, args, "compose:new");
} else { } else {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putString("action", "edit"); args.putString("action", "edit");
@ -504,7 +507,7 @@ public class FragmentCompose extends FragmentEx {
args.putLong("account", -1); args.putLong("account", -1);
args.putLong("reference", -1); args.putLong("reference", -1);
args.putLong("answer", -1); args.putLong("answer", -1);
draftLoader.execute(this, args, "draft:edit"); draftLoader.execute(this, args, "compose:edit");
} }
} else { } else {
working = savedInstanceState.getLong("working"); working = savedInstanceState.getLong("working");
@ -514,7 +517,7 @@ public class FragmentCompose extends FragmentEx {
args.putLong("account", -1); args.putLong("account", -1);
args.putLong("reference", -1); args.putLong("reference", -1);
args.putLong("answer", -1); args.putLong("answer", -1);
draftLoader.execute(this, args, "draft:instance"); draftLoader.execute(this, args, "compose:instance");
} }
} }
@ -614,6 +617,9 @@ public class FragmentCompose extends FragmentEx {
case R.id.menu_encrypt: case R.id.menu_encrypt:
onAction(R.id.menu_encrypt); onAction(R.id.menu_encrypt);
return true; return true;
case R.id.menu_send_after:
onMenuSendAfter();
return true;
default: default:
return super.onOptionsItemSelected(item); return super.onOptionsItemSelected(item);
} }
@ -689,6 +695,62 @@ public class FragmentCompose extends FragmentEx {
} }
} }
private void onMenuSendAfter() {
final View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_duration, null);
final NumberPicker npHours = dview.findViewById(R.id.npHours);
final NumberPicker npDays = dview.findViewById(R.id.npDays);
npHours.setMinValue(0);
npHours.setMaxValue(24);
npDays.setMinValue(0);
npDays.setMaxValue(90);
new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner())
.setTitle(R.string.title_send_after)
.setView(dview)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
try {
int hours = npHours.getValue();
int days = npDays.getValue();
long duration = (hours + days * 24) * 3600L * 1000L;
Bundle args = new Bundle();
args.putLong("id", working);
args.putLong("wakeup", new Date().getTime() + duration);
new SimpleTask<Void>() {
@Override
protected Void onExecute(Context context, Bundle args) {
long id = args.getLong("id");
Long wakeup = args.getLong("wakeup");
DB db = DB.getInstance(context);
db.message().setMessageSnoozed(id, wakeup);
return null;
}
@Override
protected void onExecuted(Bundle args, Void data) {
onAction(R.id.action_send);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(FragmentCompose.this, args, "compose:send:after");
} catch (Throwable ex) {
Log.e(ex);
}
}
})
.show();
}
private void onMenuImage() { private void onMenuImage() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE); intent.addCategory(Intent.CATEGORY_OPENABLE);
@ -716,7 +778,7 @@ public class FragmentCompose extends FragmentEx {
grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE); grpAddresses.setVisibility(grpAddresses.getVisibility() == View.GONE ? View.VISIBLE : View.GONE);
} }
private void onDelete() { private void onActionDelete() {
new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner()) new DialogBuilderLifecycle(getContext(), getViewLifecycleOwner())
.setMessage(R.string.title_ask_discard) .setMessage(R.string.title_ask_discard)
.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
@ -915,7 +977,7 @@ public class FragmentCompose extends FragmentEx {
else else
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
} }
}.execute(this, args, "encrypt"); }.execute(this, args, "compose:encrypt");
} }
@Override @Override
@ -1043,7 +1105,7 @@ public class FragmentCompose extends FragmentEx {
else else
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
} }
}.execute(this, args, "add:attachment"); }.execute(this, args, "compose:attachment:add");
} }
private void handleExit() { private void handleExit() {
@ -1101,7 +1163,7 @@ public class FragmentCompose extends FragmentEx {
dirty = false; dirty = false;
Log.i("Run execute id=" + working); Log.i("Run execute id=" + working);
actionLoader.execute(this, args, "action:" + action); actionLoader.execute(this, args, "compose:action:" + action);
} }
private boolean isEmpty() { private boolean isEmpty() {
@ -1658,7 +1720,7 @@ public class FragmentCompose extends FragmentEx {
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
} }
}.execute(this, args, "draft:check"); }.execute(this, args, "compose:check");
} }
private void showDraft(EntityMessage draft) { private void showDraft(EntityMessage draft) {
@ -1729,7 +1791,7 @@ public class FragmentCompose extends FragmentEx {
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
} }
}.execute(FragmentCompose.this, args, "draft:show"); }.execute(FragmentCompose.this, args, "compose:show");
} }
private SimpleTask<EntityMessage> actionLoader = new SimpleTask<EntityMessage>() { private SimpleTask<EntityMessage> actionLoader = new SimpleTask<EntityMessage>() {
@ -1926,19 +1988,17 @@ public class FragmentCompose extends FragmentEx {
Helper.copy(file, EntityAttachment.getFile(context, attachment.id)); Helper.copy(file, EntityAttachment.getFile(context, attachment.id));
} }
EntityOperation.queue(context, db, draft, EntityOperation.SEND); if (draft.ui_snoozed == null)
EntityOperation.queue(context, db, draft, EntityOperation.SEND);
if (draft.replying != null) { if (draft.ui_snoozed == null) {
EntityMessage replying = db.message().getMessage(draft.replying); Handler handler = new Handler(context.getMainLooper());
EntityOperation.queue(context, db, replying, EntityOperation.ANSWERED, true); handler.post(new Runnable() {
public void run() {
Toast.makeText(context, R.string.title_queued, Toast.LENGTH_LONG).show();
}
});
} }
Handler handler = new Handler(context.getMainLooper());
handler.post(new Runnable() {
public void run() {
Toast.makeText(context, R.string.title_queued, Toast.LENGTH_LONG).show();
}
});
} }
db.setTransactionSuccessful(); db.setTransactionSuccessful();
@ -1946,6 +2006,11 @@ public class FragmentCompose extends FragmentEx {
db.endTransaction(); db.endTransaction();
} }
if (action == R.id.action_send && draft.ui_snoozed != null) {
Log.i("Delayed send id=" + draft.id + " at " + new Date(draft.ui_snoozed));
EntityMessage.snooze(getContext(), draft.id, draft.ui_snoozed);
}
return draft; return draft;
} }
@ -2046,7 +2111,7 @@ public class FragmentCompose extends FragmentEx {
protected void onException(Bundle args, Throwable ex) { protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex); Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
} }
}.execute(FragmentCompose.this, args, source); }.execute(FragmentCompose.this, args, "compose:cid:" + source);
} }
}); });
} else } else

@ -391,7 +391,12 @@ public class ServiceSynchronize extends LifecycleService {
break; break;
case "snooze": case "snooze":
db.message().setMessageSnoozed(message.id, null); EntityFolder folder = db.folder().getFolder(message.folder);
if (EntityFolder.OUTBOX.equals(folder.type)) {
Log.i("Delayed send id=" + message.id);
EntityOperation.queue(ServiceSynchronize.this, db, message, EntityOperation.SEND);
} else
db.message().setMessageSnoozed(message.id, null);
break; break;
default: default:
@ -1826,6 +1831,11 @@ public class ServiceSynchronize extends LifecycleService {
} }
} }
if (message.replying != null) {
EntityMessage replying = db.message().getMessage(message.replying);
EntityOperation.queue(this, db, replying, EntityOperation.ANSWERED, true);
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

@ -18,4 +18,10 @@
android:icon="@drawable/baseline_lock_24" android:icon="@drawable/baseline_lock_24"
android:title="@string/title_encrypt" android:title="@string/title_encrypt"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" />
<item
android:id="@+id/menu_send_after"
android:icon="@drawable/baseline_lock_24"
android:title="@string/title_send_after"
app:showAsAction="never" />
</menu> </menu>

@ -315,6 +315,7 @@
<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_view">View</string> <string name="title_view">View</string>
<string name="title_send_after">Send after &#8230;</string>
<string name="title_no_selection">Nothing selected</string> <string name="title_no_selection">Nothing selected</string>
<string name="title_style_bold">Bold</string> <string name="title_style_bold">Bold</string>

Loading…
Cancel
Save