Added cross account move

pull/147/head
M66B 7 years ago
parent ad12d60b06
commit b0c1747bbe

@ -133,7 +133,6 @@ FairEmail follows all the best practices for an email client as decribed in [thi
* [(69) Can you add auto scroll up on new message?](#user-content-faq69) * [(69) Can you add auto scroll up on new message?](#user-content-faq69)
* [(70) When will messages be auto expanded?](#user-content-faq70) * [(70) When will messages be auto expanded?](#user-content-faq70)
* [(71) How do I use filter rules?](#user-content-faq71) * [(71) How do I use filter rules?](#user-content-faq71)
* [(72) Is moving messages between accounts supported?](#user-content-faq72)
[I have another question.](#support) [I have another question.](#support)
@ -1136,14 +1135,6 @@ Using filter rules is a pro feature.
<br /> <br />
<a name="faq72"></a>
**(72) Is moving messages between accounts supported?**
Moving messages between accounts is not supported because this can result in lost or duplicate messages.
A message structure or message content supported by one provider might not be supported by another provider.
<br />
## Support ## Support

@ -28,6 +28,8 @@ import android.os.Build;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.Serializable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.room.Entity; import androidx.room.Entity;
@ -38,7 +40,7 @@ import androidx.room.PrimaryKey;
indices = { indices = {
} }
) )
public class EntityAccount { public class EntityAccount implements Serializable {
static final String TABLE_NAME = "account"; static final String TABLE_NAME = "account";
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)

@ -127,6 +127,7 @@ public class EntityOperation {
} }
private static void queue(Context context, DB db, EntityMessage message, String name, JSONArray jargs) { private static void queue(Context context, DB db, EntityMessage message, String name, JSONArray jargs) {
long folder = message.folder;
try { try {
if (SEEN.equals(name)) { if (SEEN.equals(name)) {
for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid)) { for (EntityMessage similar : db.message().getMessageByMsgId(message.account, message.msgid)) {
@ -166,12 +167,14 @@ public class EntityOperation {
long id = message.id; long id = message.id;
long uid = message.uid; long uid = message.uid;
message.id = null; message.id = null;
message.uid = null; message.account = target.account;
message.folder = target.id; message.folder = target.id;
message.uid = null;
long newid = db.message().insertMessage(message); long newid = db.message().insertMessage(message);
message.id = id; message.id = id;
message.uid = uid; message.account = source.account;
message.folder = source.id; message.folder = source.id;
message.uid = uid;
if (message.content) if (message.content)
try { try {
@ -186,6 +189,13 @@ public class EntityOperation {
EntityAttachment.copy(context, db, message.id, newid); EntityAttachment.copy(context, db, message.id, newid);
} }
// Cross account move
if (!source.account.equals(target.account)) {
name = ADD;
folder = target.id;
jargs.remove(0);
}
} else if (DELETE.equals(name)) } else if (DELETE.equals(name))
db.message().setMessageUiHide(message.id, true); db.message().setMessageUiHide(message.id, true);
} catch (JSONException ex) { } catch (JSONException ex) {
@ -193,7 +203,7 @@ public class EntityOperation {
} }
EntityOperation operation = new EntityOperation(); EntityOperation operation = new EntityOperation();
operation.folder = message.folder; operation.folder = folder;
operation.message = message.id; operation.message = message.id;
operation.name = name; operation.name = name;
operation.args = jargs.toString(); operation.args = jargs.toString();

@ -44,6 +44,7 @@ import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuInflater; import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.SubMenu;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.CheckBox; import android.widget.CheckBox;
@ -57,6 +58,7 @@ import com.google.android.material.snackbar.Snackbar;
import java.text.Collator; import java.text.Collator;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
@ -88,7 +90,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
public class FragmentMessages extends FragmentBase { public class FragmentMessages extends FragmentBase {
private ViewGroup view; private ViewGroup view;
private SwipeRefreshLayout swipeRefresh; private SwipeRefreshLayout swipeRefresh;
private View popupAnchor;
private TextView tvSupport; private TextView tvSupport;
private ImageButton ibHintSupport; private ImageButton ibHintSupport;
private ImageButton ibHintSwipe; private ImageButton ibHintSwipe;
@ -199,7 +200,6 @@ public class FragmentMessages extends FragmentBase {
// Get controls // Get controls
swipeRefresh = view.findViewById(R.id.swipeRefresh); swipeRefresh = view.findViewById(R.id.swipeRefresh);
popupAnchor = view.findViewById(R.id.popupAnchor);
tvSupport = view.findViewById(R.id.tvSupport); tvSupport = view.findViewById(R.id.tvSupport);
ibHintSupport = view.findViewById(R.id.ibHintSupport); ibHintSupport = view.findViewById(R.id.ibHintSupport);
ibHintSwipe = view.findViewById(R.id.ibHintSwipe); ibHintSwipe = view.findViewById(R.id.ibHintSwipe);
@ -530,15 +530,17 @@ public class FragmentMessages extends FragmentBase {
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
EntityFolder folder = null; EntityFolder target = null;
if (message != null) if (message != null)
if (type) if (type)
folder = db.folder().getFolderByType(message.account, name); target = db.folder().getFolderByType(message.account, name);
else else
folder = db.folder().getFolderByName(message.account, name); target = db.folder().getFolderByName(message.account, name);
if (folder != null) if (target != null) {
result.add(new MessageTarget(id, folder)); EntityAccount account = db.account().getAccount(target.account);
result.add(new MessageTarget(id, account, target));
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
@ -650,7 +652,7 @@ public class FragmentMessages extends FragmentBase {
protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) { protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) {
long id = args.getLong("id"); long id = args.getLong("id");
boolean thread = args.getBoolean("thread"); boolean thread = args.getBoolean("thread");
long target = args.getLong("target"); long tid = args.getLong("target");
ArrayList<MessageTarget> result = new ArrayList<>(); ArrayList<MessageTarget> result = new ArrayList<>();
@ -659,14 +661,15 @@ public class FragmentMessages extends FragmentBase {
try { try {
db.beginTransaction(); db.beginTransaction();
EntityFolder folder = db.folder().getFolder(target); EntityFolder target = db.folder().getFolder(tid);
if (folder != null) { if (target != null) {
EntityAccount account = db.account().getAccount(target.account);
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
if (message != null) { if (message != null) {
List<EntityMessage> messages = db.message().getMessageByThread( List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading && thread ? null : id, message.folder); message.account, message.thread, threading && thread ? null : id, message.folder);
for (EntityMessage threaded : messages) { for (EntityMessage threaded : messages) {
result.add(new MessageTarget(threaded.id, folder)); result.add(new MessageTarget(threaded.id, account, target));
db.message().setMessageUiHide(threaded.id, true); db.message().setMessageUiHide(threaded.id, true);
// Prevent new message notification on undo // Prevent new message notification on undo
db.message().setMessageUiIgnored(threaded.id, true); db.message().setMessageUiIgnored(threaded.id, true);
@ -721,15 +724,15 @@ public class FragmentMessages extends FragmentBase {
args.putLong("account", account); args.putLong("account", account);
args.putString("thread", thread); args.putString("thread", thread);
args.putLong("id", id); args.putLong("id", id);
args.putString("folderType", folderType); args.putString("type", folderType);
new SimpleTask<ArrayList<MessageTarget>>() { new SimpleTask<ArrayList<MessageTarget>>() {
@Override @Override
protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) { protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) {
long account = args.getLong("account"); long aid = args.getLong("account");
String thread = args.getString("thread"); String thread = args.getString("thread");
long id = args.getLong("id"); long id = args.getLong("id");
String folderType = args.getString("folderType"); String type = args.getString("type");
ArrayList<MessageTarget> result = new ArrayList<>(); ArrayList<MessageTarget> result = new ArrayList<>();
@ -737,10 +740,11 @@ public class FragmentMessages extends FragmentBase {
try { try {
db.beginTransaction(); db.beginTransaction();
EntityFolder target = db.folder().getFolderByType(account, folderType); EntityFolder target = db.folder().getFolderByType(aid, type);
if (target != null) { if (target != null) {
EntityAccount account = db.account().getAccount(target.account);
List<EntityMessage> messages = db.message().getMessageByThread( List<EntityMessage> messages = db.message().getMessageByThread(
account, thread, threading ? null : id, null); aid, thread, threading ? null : id, null);
for (EntityMessage threaded : messages) { for (EntityMessage threaded : messages) {
EntityFolder folder = db.folder().getFolder(threaded.folder); EntityFolder folder = db.folder().getFolder(threaded.folder);
if (!target.id.equals(threaded.folder) && if (!target.id.equals(threaded.folder) &&
@ -749,7 +753,7 @@ public class FragmentMessages extends FragmentBase {
(!EntityFolder.SENT.equals(folder.type) || EntityFolder.TRASH.equals(target.type)) && (!EntityFolder.SENT.equals(folder.type) || EntityFolder.TRASH.equals(target.type)) &&
!EntityFolder.TRASH.equals(folder.type) && !EntityFolder.TRASH.equals(folder.type) &&
!EntityFolder.JUNK.equals(folder.type)) !EntityFolder.JUNK.equals(folder.type))
result.add(new MessageTarget(threaded.id, target)); result.add(new MessageTarget(threaded.id, account, target));
} }
} }
@ -778,79 +782,118 @@ public class FragmentMessages extends FragmentBase {
args.putLong("folder", folder); args.putLong("folder", folder);
args.putLongArray("ids", getSelection()); args.putLongArray("ids", getSelection());
new SimpleTask<Boolean[]>() { new SimpleTask<MoreResult>() {
@Override @Override
protected Boolean[] onExecute(Context context, Bundle args) { protected MoreResult onExecute(Context context, Bundle args) {
long fid = args.getLong("folder"); long fid = args.getLong("folder");
long[] ids = args.getLongArray("ids"); long[] ids = args.getLongArray("ids");
Boolean[] result = new Boolean[12]; MoreResult result = new MoreResult();
for (int i = 0; i < result.length; i++)
result[i] = false;
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
List<Long> accounts = new ArrayList<>();
for (long id : ids) { for (long id : ids) {
EntityMessage message = db.message().getMessage(id); EntityMessage message = db.message().getMessage(id);
if (message != null) { if (message != null) {
if (!accounts.contains(message.account)) if (message.ui_seen)
accounts.add(message.account); result.seen = true;
result[message.ui_seen ? 1 : 0] = true; else
result[message.flagged ? 3 : 2] = true; result.unseen = true;
if (db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE) != null) if (message.ui_flagged)
result[4] = true; result.flagged = true;
if (db.folder().getFolderByType(message.account, EntityFolder.TRASH) != null) else
result[5] = true; result.unflagged = true;
if (db.folder().getFolderByType(message.account, EntityFolder.JUNK) != null)
result[6] = true; result.hasArchive = (result.hasArchive &&
db.folder().getFolderByType(message.account, EntityFolder.ARCHIVE) != null);
result.hasTrash = (result.hasTrash &&
db.folder().getFolderByType(message.account, EntityFolder.TRASH) != null);
result.hasJunk = (result.hasJunk &&
db.folder().getFolderByType(message.account, EntityFolder.JUNK) != null);
} }
} }
EntityFolder folder = db.folder().getFolder(fid); EntityFolder folder = db.folder().getFolder(fid);
if (folder != null) { if (folder != null) {
result[7] = EntityFolder.ARCHIVE.equals(folder.type); result.isArchive = EntityFolder.ARCHIVE.equals(folder.type);
result[8] = EntityFolder.TRASH.equals(folder.type); result.isTrash = EntityFolder.TRASH.equals(folder.type);
result[9] = EntityFolder.JUNK.equals(folder.type); result.isJunk = EntityFolder.JUNK.equals(folder.type);
result[10] = EntityFolder.DRAFTS.equals(folder.type); result.isDrafts = EntityFolder.DRAFTS.equals(folder.type);
} }
result[11] = (accounts.size() == 1); result.accounts = db.account().getAccounts(true);
final Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
Collections.sort(result.accounts, new Comparator<EntityAccount>() {
@Override
public int compare(EntityAccount a1, EntityAccount a2) {
int p = -a1.primary.compareTo(a2.primary);
if (p != 0)
return p;
return collator.compare(a1.name, a2.name);
}
});
for (EntityAccount account : result.accounts) {
List<EntityFolder> targets = new ArrayList<>();
List<EntityFolder> folders = db.folder().getFolders(account.id);
for (EntityFolder target : folders)
if (!target.hide &&
!EntityFolder.ARCHIVE.equals(target.type) &&
!EntityFolder.TRASH.equals(target.type) &&
!EntityFolder.JUNK.equals(target.type) &&
!target.id.equals(fid))
targets.add(target);
EntityFolder.sort(context, targets);
result.targets.put(account, targets);
}
return result; return result;
} }
@Override @Override
protected void onExecuted(Bundle args, final Boolean[] result) { protected void onExecuted(Bundle args, final MoreResult result) {
PopupMenu popupMenu = new PopupMenu(getContext(), fabMore); PopupMenu popupMenu = new PopupMenu(getContext(), fabMore);
if (result[0] && !result[10]) // Unseen, not draft if (result.unseen && !result.isDrafts) // Unseen, not draft
popupMenu.getMenu().add(Menu.NONE, action_seen, 1, R.string.title_seen); popupMenu.getMenu().add(Menu.NONE, action_seen, 1, R.string.title_seen);
if (result[1] && !result[10]) // Seen, not draft if (result.seen && !result.isDrafts) // Seen, not draft
popupMenu.getMenu().add(Menu.NONE, action_unseen, 2, R.string.title_unseen); popupMenu.getMenu().add(Menu.NONE, action_unseen, 2, R.string.title_unseen);
popupMenu.getMenu().add(Menu.NONE, action_snooze, 3, R.string.title_snooze); popupMenu.getMenu().add(Menu.NONE, action_snooze, 3, R.string.title_snooze);
if (result[2]) if (result.unflagged)
popupMenu.getMenu().add(Menu.NONE, action_flag, 4, R.string.title_flag); popupMenu.getMenu().add(Menu.NONE, action_flag, 4, R.string.title_flag);
if (result[3]) if (result.flagged)
popupMenu.getMenu().add(Menu.NONE, action_unflag, 5, R.string.title_unflag); popupMenu.getMenu().add(Menu.NONE, action_unflag, 5, R.string.title_unflag);
if (result[4] && !result[7] && !result[10]) // has archive and not is archive/drafts if (result.hasArchive && !result.isArchive && !result.isDrafts) // has archive and not is archive/drafts
popupMenu.getMenu().add(Menu.NONE, action_archive, 6, R.string.title_archive); popupMenu.getMenu().add(Menu.NONE, action_archive, 6, R.string.title_archive);
if (result[8]) // is trash if (result.isTrash) // is trash
popupMenu.getMenu().add(Menu.NONE, action_delete, 7, R.string.title_delete); popupMenu.getMenu().add(Menu.NONE, action_delete, 7, R.string.title_delete);
if (!result[8] && result[5]) // not trash and has trash if (!result.isTrash && result.hasTrash) // not trash and has trash
popupMenu.getMenu().add(Menu.NONE, action_trash, 8, R.string.title_trash); popupMenu.getMenu().add(Menu.NONE, action_trash, 8, R.string.title_trash);
if (result[6] && !result[9] && !result[10]) // has junk and not junk/drafts if (result.hasJunk && !result.isJunk && !result.isDrafts) // has junk and not junk/drafts
popupMenu.getMenu().add(Menu.NONE, action_junk, 9, R.string.title_spam); popupMenu.getMenu().add(Menu.NONE, action_junk, 9, R.string.title_spam);
if (!result[10]) // not drafts if (!result.isDrafts) { // not drafts
popupMenu.getMenu().add(Menu.NONE, action_move, 10, R.string.title_move); int order = 11;
for (EntityAccount account : result.accounts) {
SubMenu smenu = popupMenu.getMenu()
.addSubMenu(Menu.NONE, 0, order++, getString(R.string.title_move_to, account.name));
int sorder = 1;
for (EntityFolder target : result.targets.get(account)) {
MenuItem item = smenu.add(Menu.NONE, action_move, sorder++, target.getDisplayName(getContext()));
item.setIntent(new Intent().putExtra("target", target.id));
}
}
}
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override @Override
@ -884,10 +927,7 @@ public class FragmentMessages extends FragmentBase {
onActionJunkSelection(); onActionJunkSelection();
return true; return true;
case action_move: case action_move:
if (result[11]) // single account onActionMoveSelection(target.getIntent().getLongExtra("target", -1));
onActionMoveSelection();
else
Snackbar.make(view, R.string.title_no_cross_account, Snackbar.LENGTH_LONG).show();
return true; return true;
default: default:
return false; return false;
@ -1142,8 +1182,8 @@ public class FragmentMessages extends FragmentBase {
message.account, message.thread, threading ? null : id, message.folder); message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages) { for (EntityMessage threaded : messages) {
EntityFolder target = db.folder().getFolderByType(message.account, type); EntityFolder target = db.folder().getFolderByType(message.account, type);
if (target != null) EntityAccount account = db.account().getAccount(target.account);
result.add(new MessageTarget(threaded.id, target)); result.add(new MessageTarget(threaded.id, account, target));
} }
} }
} }
@ -1168,107 +1208,50 @@ public class FragmentMessages extends FragmentBase {
}.execute(FragmentMessages.this, args, "messages:move"); }.execute(FragmentMessages.this, args, "messages:move");
} }
private void onActionMoveSelection() { private void onActionMoveSelection(long target) {
Bundle args = new Bundle(); Bundle args = new Bundle();
args.putLong("folder", folder);
args.putLongArray("ids", getSelection()); args.putLongArray("ids", getSelection());
args.putLong("target", target);
new SimpleTask<List<EntityFolder>>() { selectionTracker.clearSelection();
new SimpleTask<ArrayList<MessageTarget>>() {
@Override @Override
protected List<EntityFolder> onExecute(Context context, Bundle args) { protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) {
long fid = args.getLong("folder");
long[] ids = args.getLongArray("ids"); long[] ids = args.getLongArray("ids");
long tid = args.getLong("target");
ArrayList<MessageTarget> result = new ArrayList<>();
DB db = DB.getInstance(context); DB db = DB.getInstance(context);
try {
db.beginTransaction();
long account = -1; EntityFolder target = db.folder().getFolder(tid);
for (long id : ids) { if (target != null) {
EntityMessage message = db.message().getMessage(id); EntityAccount account = db.account().getAccount(target.account);
if (message != null) { for (long id : ids) {
account = message.account; EntityMessage message = db.message().getMessage(id);
break; if (message != null) {
List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages)
result.add(new MessageTarget(threaded.id, account, target));
}
}
} }
}
List<EntityFolder> folders = db.folder().getFolders(account); db.setTransactionSuccessful();
} finally {
List<EntityFolder> targets = new ArrayList<>(); db.endTransaction();
for (EntityFolder folder : folders) }
if (!folder.hide &&
!EntityFolder.ARCHIVE.equals(folder.type) &&
!EntityFolder.TRASH.equals(folder.type) &&
!EntityFolder.JUNK.equals(folder.type) &&
(fid < 0 ? !folder.unified : !folder.id.equals(fid)))
targets.add(folder);
EntityFolder.sort(context, targets);
return targets; return result;
} }
@Override @Override
protected void onExecuted(final Bundle args, List<EntityFolder> folders) { protected void onExecuted(Bundle args, ArrayList<MessageTarget> result) {
PopupMenu popupMenu = new PopupMenu(getContext(), popupAnchor); moveAsk(result);
int order = 0;
for (EntityFolder folder : folders)
popupMenu.getMenu().add(Menu.NONE, folder.id.intValue(), order++, folder.getDisplayName(getContext()));
popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(final MenuItem target) {
args.putLong("target", target.getItemId());
selectionTracker.clearSelection();
new SimpleTask<ArrayList<MessageTarget>>() {
@Override
protected ArrayList<MessageTarget> onExecute(Context context, Bundle args) {
long[] ids = args.getLongArray("ids");
long target = args.getLong("target");
ArrayList<MessageTarget> result = new ArrayList<>();
DB db = DB.getInstance(context);
try {
db.beginTransaction();
EntityFolder folder = db.folder().getFolder(target);
if (folder != null)
for (long id : ids) {
EntityMessage message = db.message().getMessage(id);
if (message != null) {
List<EntityMessage> messages = db.message().getMessageByThread(
message.account, message.thread, threading ? null : id, message.folder);
for (EntityMessage threaded : messages)
result.add(new MessageTarget(threaded.id, folder));
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
return result;
}
@Override
protected void onExecuted(Bundle args, ArrayList<MessageTarget> result) {
moveAsk(result);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(FragmentMessages.this, args, "messages:move");
return true;
}
});
popupMenu.show();
} }
@Override @Override
@ -2180,14 +2163,12 @@ public class FragmentMessages extends FragmentBase {
} }
private String getDisplay(ArrayList<MessageTarget> result) { private String getDisplay(ArrayList<MessageTarget> result) {
List<EntityFolder> folders = new ArrayList<>();
for (MessageTarget target : result)
if (!folders.contains(target.folder))
folders.add(target.folder);
List<String> displays = new ArrayList<>(); List<String> displays = new ArrayList<>();
for (EntityFolder folder : folders) for (MessageTarget target : result) {
displays.add(folder.getDisplayName(getContext())); String display = target.account.name + "/" + target.folder.getDisplayName(getContext());
if (!displays.contains(display))
displays.add(display);
}
Collator collator = Collator.getInstance(Locale.getDefault()); Collator collator = Collator.getInstance(Locale.getDefault());
collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc collator.setStrength(Collator.SECONDARY); // Case insensitive, process accents etc
@ -2216,23 +2197,43 @@ public class FragmentMessages extends FragmentBase {
} }
}; };
static class MessageTarget implements Parcelable { private class MoreResult {
boolean seen;
boolean unseen;
boolean flagged;
boolean unflagged;
boolean hasArchive = true;
boolean hasTrash = true;
boolean hasJunk = true;
boolean isArchive;
boolean isTrash;
boolean isJunk;
boolean isDrafts;
List<EntityAccount> accounts;
Map<EntityAccount, List<EntityFolder>> targets = new HashMap<>();
}
private static class MessageTarget implements Parcelable {
long id; long id;
EntityAccount account;
EntityFolder folder; EntityFolder folder;
MessageTarget(long id, EntityFolder folder) { MessageTarget(long id, EntityAccount account, EntityFolder folder) {
this.id = id; this.id = id;
this.account = account;
this.folder = folder; this.folder = folder;
} }
protected MessageTarget(Parcel in) { protected MessageTarget(Parcel in) {
id = in.readLong(); id = in.readLong();
account = (EntityAccount) in.readSerializable();
folder = (EntityFolder) in.readSerializable(); folder = (EntityFolder) in.readSerializable();
} }
@Override @Override
public void writeToParcel(Parcel dest, int flags) { public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(id); dest.writeLong(id);
dest.writeSerializable(account);
dest.writeSerializable(folder); dest.writeSerializable(folder);
} }

@ -1483,7 +1483,7 @@ public class ServiceSynchronize extends LifecycleService {
doKeyword(folder, ifolder, message, jargs, db); doKeyword(folder, ifolder, message, jargs, db);
else if (EntityOperation.ADD.equals(op.name)) else if (EntityOperation.ADD.equals(op.name))
doAdd(folder, isession, istore, ifolder, message, jargs, db); doAdd(folder, isession, ifolder, message, jargs, db);
else if (EntityOperation.MOVE.equals(op.name)) else if (EntityOperation.MOVE.equals(op.name))
doMove(folder, isession, istore, ifolder, message, jargs, db); doMove(folder, isession, istore, ifolder, message, jargs, db);
@ -1663,7 +1663,7 @@ public class ServiceSynchronize extends LifecycleService {
} }
} }
private void doAdd(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException { private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException {
// Append message // Append message
MimeMessage imessage = MessageHelper.from(this, message, isession); MimeMessage imessage = MessageHelper.from(this, message, isession);
@ -1673,6 +1673,10 @@ public class ServiceSynchronize extends LifecycleService {
} }
ifolder.appendMessages(new Message[]{imessage}); ifolder.appendMessages(new Message[]{imessage});
// Cross account move
if (!folder.id.equals(message.folder))
EntityOperation.queue(this, db, message, EntityOperation.DELETE);
} }
private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException { private void doMove(EntityFolder folder, Session isession, IMAPStore istore, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws JSONException, MessagingException, IOException {

@ -15,13 +15,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<View
android:id="@+id/popupAnchor"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView <TextView
android:id="@+id/tvSupport" android:id="@+id/tvSupport"
android:layout_width="wrap_content" android:layout_width="wrap_content"

@ -293,6 +293,7 @@
<string name="title_more">More</string> <string name="title_more">More</string>
<string name="title_spam">Spam</string> <string name="title_spam">Spam</string>
<string name="title_move">Move</string> <string name="title_move">Move</string>
<string name="title_move_to">Move to %1$s</string>
<string name="title_snooze">Snooze &#8230;</string> <string name="title_snooze">Snooze &#8230;</string>
<string name="title_archive">Archive</string> <string name="title_archive">Archive</string>
<string name="title_reply">Reply</string> <string name="title_reply">Reply</string>
@ -305,7 +306,6 @@
<string name="title_no_stream">An outdated app sent a file path instead of a file stream</string> <string name="title_no_stream">An outdated app sent a file path instead of a file stream</string>
<string name="title_no_contacts">Contact picker not available</string> <string name="title_no_contacts">Contact picker not available</string>
<string name="title_no_internet">No internet connection</string> <string name="title_no_internet">No internet connection</string>
<string name="title_no_cross_account">Moving across accounts is not supported</string>
<string name="title_raw_saved">Raw message saved</string> <string name="title_raw_saved">Raw message saved</string>
<string name="title_attachment_saved">Attachment saved</string> <string name="title_attachment_saved">Attachment saved</string>
<string name="title_attachments_saved">Attachments saved</string> <string name="title_attachments_saved">Attachments saved</string>

Loading…
Cancel
Save