MessageList refactoring to remove duplicate code paths.

Message operations should be more consistent now, regardless of how
the messages are selected (long click, checkbox+Menu, future group selection).

This is a backport of the modifications made on the issue258 branch,
without the threading specific features (no new feature introduced).
This commit is contained in:
Fiouz 2011-06-04 22:57:03 +02:00
parent 5d85b7a508
commit 821a00e727
1 changed files with 342 additions and 296 deletions

View File

@ -22,33 +22,33 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log;
import android.util.TypedValue;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.animation.Animation.AnimationListener;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.View.OnClickListener;
import android.view.animation.Animation;
import android.view.animation.Animation.AnimationListener;
import android.view.animation.AnimationUtils;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageButton;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.Account;
import com.fsck.k9.AccountStats;
@ -61,16 +61,16 @@ import com.fsck.k9.activity.setup.AccountSettings;
import com.fsck.k9.activity.setup.FolderSettings;
import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.controller.MessagingController;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.controller.MessagingController.SORT_TYPE;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore;
import com.fsck.k9.mail.store.StorageManager;
import com.fsck.k9.mail.store.LocalStore.LocalFolder;
import com.fsck.k9.mail.store.StorageManager;
/**
@ -207,8 +207,6 @@ public class MessageList
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2;
private static final int ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH = 3;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY_BATCH = 4;
private static final String EXTRA_ACCOUNT = "account";
private static final String EXTRA_FOLDER = "folder";
@ -298,9 +296,21 @@ public class MessageList
private FontSizes mFontSizes = K9.getFontSizes();
private Bundle mState = null;
private MessageInfoHolder mSelectedMessage = null;
private Context context = null;
/**
* Remember the selection to be consistent between menu display and menu item
* selection
*/
private MessageInfoHolder mSelectedMessage;
/**
* Relevant messages for the current context when we have to remember the
* chosen messages between user interactions (eg. Selecting a folder for
* move operation)
*/
private List<MessageInfoHolder> mActiveMessages;
private Context context;
/* package visibility for faster inner class access */
MessageHelper mMessageHelper = MessageHelper.getInstance(this);
@ -327,7 +337,13 @@ public class MessageList
}
class MessageListHandler {
public void removeMessage(final List<MessageInfoHolder> messages) {
/**
* @param messages Never {@code null}.
*/
public void removeMessages(final List<MessageInfoHolder> messages) {
if (messages.isEmpty()) {
return;
}
runOnUiThread(new Runnable() {
public void run() {
for (MessageInfoHolder message : messages) {
@ -348,7 +364,13 @@ public class MessageList
});
}
/**
* @param messages Never {@code null}.
*/
public void addMessages(final List<MessageInfoHolder> messages) {
if (messages.isEmpty()) {
return;
}
final boolean wasEmpty = mAdapter.messages.isEmpty();
runOnUiThread(new Runnable() {
public void run() {
@ -577,7 +599,7 @@ public class MessageList
if (mSelectedCount > 0) {
// In multiselect mode make sure that clicking on the item results
// in toggling the 'selected' checkbox.
setSelected(message, !message.selected);
setSelected(Collections.singletonList(message), !message.selected);
} else {
onOpenMessage(message);
}
@ -913,18 +935,20 @@ public class MessageList
if (position >= 0) {
MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position);
final List<MessageInfoHolder> selection = getSelectionFromMessage(message);
if (message != null) {
switch (keyCode) {
case KeyEvent.KEYCODE_DEL: {
onDelete(message, position);
onDelete(selection);
return true;
}
case KeyEvent.KEYCODE_S: {
setSelected(message, !message.selected);
setSelected(selection, !message.selected);
return true;
}
case KeyEvent.KEYCODE_D: {
onDelete(message, position);
onDelete(selection);
return true;
}
case KeyEvent.KEYCODE_F: {
@ -940,23 +964,23 @@ public class MessageList
return true;
}
case KeyEvent.KEYCODE_G: {
onToggleFlag(message);
setFlag(selection, Flag.FLAGGED, !message.flagged);
return true;
}
case KeyEvent.KEYCODE_M: {
onMove(message);
onMove(selection);
return true;
}
case KeyEvent.KEYCODE_V: {
onArchive(message);
onArchive(selection);
return true;
}
case KeyEvent.KEYCODE_Y: {
onCopy(message);
onCopy(selection);
return true;
}
case KeyEvent.KEYCODE_Z: {
onToggleRead(message);
setFlag(selection, Flag.SEEN, !message.read);
return true;
}
}
@ -1097,150 +1121,57 @@ public class MessageList
reSort();
}
private void onDelete(MessageInfoHolder holder, int position) {
mAdapter.removeMessage(holder);
mController.deleteMessages(new Message[] { holder.message }, null);
}
private void onMove(MessageInfoHolder holder) {
if (!mController.isMoveCapable(holder.message.getFolder().getAccount())) {
return;
/**
* @param holders
* Never {@code null}.
*/
private void onDelete(final List<MessageInfoHolder> holders) {
// FIXME: removeMessage does it own check on the 'selected' field, we're duplicating the logic here...
final List<Message> messagesToRemove = new ArrayList<Message>();
for (MessageInfoHolder holder : holders) {
if (holder.selected) {
messagesToRemove.add(holder.message);
}
}
if (!mController.isMoveCapable(holder.message)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
final Account account = holder.message.getFolder().getAccount();
Intent intent = new Intent(this, ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, holder.folder.name);
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName());
intent.putExtra(ChooseFolder.EXTRA_MESSAGE, holder.message.makeMessageReference());
startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_MOVE);
}
private void onArchive(MessageInfoHolder holder) {
if (!mController.isMoveCapable(holder.message)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
onMoveChosen(holder, holder.message.getFolder().getAccount().getArchiveFolderName());
}
private void onSpam(MessageInfoHolder holder) {
if (K9.confirmSpam()) {
// The action handler needs this to move the message later
mSelectedMessage = holder;
showDialog(R.id.dialog_confirm_spam);
} else {
moveToSpamFolder(holder);
}
}
private void moveToSpamFolder(MessageInfoHolder holder) {
if (!mController.isMoveCapable(holder.message)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
onMoveChosen(holder, holder.message.getFolder().getAccount().getSpamFolderName());
}
private void onCopy(MessageInfoHolder holder) {
if (!mController.isCopyCapable(holder.message.getFolder().getAccount())) {
return;
}
if (!mController.isCopyCapable(holder.message)) {
Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
final Account account = holder.message.getFolder().getAccount();
Intent intent = new Intent(this, ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, account.getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, holder.folder.name);
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, account.getLastSelectedFolderName());
intent.putExtra(ChooseFolder.EXTRA_MESSAGE, holder.message.makeMessageReference());
startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_COPY);
mHandler.removeMessages(holders);
mController.deleteMessages(messagesToRemove.toArray(EMPTY_MESSAGE_ARRAY), null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK)
if (resultCode != RESULT_OK) {
return;
}
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
case ACTIVITY_CHOOSE_FOLDER_COPY: {
if (data == null)
if (data == null) {
return;
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
final MessageReference ref = data.getParcelableExtra(ChooseFolder.EXTRA_MESSAGE);
final MessageInfoHolder m = mAdapter.getMessage(ref);
if ((destFolderName != null) && (m != null)) {
final Account account = m.message.getFolder().getAccount();
if (destFolderName != null) {
final List<MessageInfoHolder> holders = mActiveMessages;
mActiveMessages = null; // don't need it any more
final Account account = holders.get(0).message.getFolder().getAccount();
account.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE:
onMoveChosen(m, destFolderName);
move(holders, destFolderName);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY:
onCopyChosen(m, destFolderName);
copy(holders, destFolderName);
break;
}
}
break;
}
case ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH:
case ACTIVITY_CHOOSE_FOLDER_COPY_BATCH: {
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER);
final String accountUuid = data.getStringExtra(ChooseFolder.EXTRA_ACCOUNT);
final Account account = Preferences.getPreferences(this).getAccount(accountUuid);
account.setLastSelectedFolderName(destFolderName);
switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH:
onMoveChosenBatch(destFolderName);
break;
case ACTIVITY_CHOOSE_FOLDER_COPY_BATCH:
onCopyChosenBatch(destFolderName);
break;
}
}
}
}
private void onMoveChosen(MessageInfoHolder holder, String folderName) {
if (mController.isMoveCapable(holder.message.getFolder().getAccount()) && folderName != null) {
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
mAdapter.removeMessage(holder);
mController.moveMessage(holder.message.getFolder().getAccount(), holder.message.getFolder().getName(), holder.message, folderName, null);
}
}
private void onCopyChosen(MessageInfoHolder holder, String folderName) {
if (mController.isCopyCapable(holder.message.getFolder().getAccount()) && folderName != null) {
mController.copyMessage(holder.message.getFolder().getAccount(),
holder.message.getFolder().getName(), holder.message, folderName, null);
}
}
@ -1308,9 +1239,9 @@ public class MessageList
new Runnable() {
@Override
public void run() {
moveToSpamFolder(mSelectedMessage);
onSpamConfirmed(mActiveMessages);
// No further need for this reference
mSelectedMessage = null;
mActiveMessages = null;
}
});
}
@ -1353,6 +1284,7 @@ public class MessageList
@Override
public boolean onOptionsItemSelected(MenuItem item) {
final List<MessageInfoHolder> selection = getSelectionFromCheckboxes();
int itemId = item.getItemId();
switch (itemId) {
case R.id.compose: {
@ -1399,23 +1331,23 @@ public class MessageList
return true;
}
case R.id.batch_delete_op: {
deleteSelected();
onDelete(selection);
return true;
}
case R.id.batch_mark_read_op: {
flagSelected(Flag.SEEN, true);
setFlag(selection, Flag.SEEN, true);
return true;
}
case R.id.batch_mark_unread_op: {
flagSelected(Flag.SEEN, false);
setFlag(selection, Flag.SEEN, false);
return true;
}
case R.id.batch_flag_op: {
flagSelected(Flag.FLAGGED, true);
setFlag(selection, Flag.FLAGGED, true);
return true;
}
case R.id.batch_unflag_op: {
flagSelected(Flag.FLAGGED, false);
setFlag(selection, Flag.FLAGGED, false);
return true;
}
case R.id.app_settings: {
@ -1462,19 +1394,19 @@ public class MessageList
return true;
}
case R.id.batch_copy_op: {
onCopyBatch();
onCopy(selection);
return true;
}
case R.id.batch_archive_op: {
onArchiveBatch();
onArchive(selection);
return true;
}
case R.id.batch_spam_op: {
onSpamBatch();
onSpam(selection);
return true;
}
case R.id.batch_move_op: {
onMoveBatch();
onMove(selection);
return true;
}
case R.id.expunge: {
@ -1564,28 +1496,26 @@ public class MessageList
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
MessageInfoHolder holder = mSelectedMessage;
final MessageInfoHolder holder = mSelectedMessage == null ? (MessageInfoHolder) mAdapter.getItem(info.position) : mSelectedMessage;
// don't need this anymore
mSelectedMessage = null;
if (holder == null) {
holder = (MessageInfoHolder) mAdapter.getItem(info.position);
}
final List<MessageInfoHolder> selection = getSelectionFromMessage(holder);
switch (item.getItemId()) {
case R.id.open: {
onOpenMessage(holder);
break;
}
case R.id.select: {
setSelected(holder, true);
setSelected(selection, true);
break;
}
case R.id.deselect: {
setSelected(holder, false);
setSelected(selection, false);
break;
}
case R.id.delete: {
onDelete(holder, info.position);
onDelete(selection);
break;
}
case R.id.reply: {
@ -1614,19 +1544,19 @@ public class MessageList
break;
}
case R.id.archive: {
onArchive(holder);
onArchive(selection);
break;
}
case R.id.spam: {
onSpam(holder);
onSpam(selection);
break;
}
case R.id.move: {
onMove(holder);
onMove(selection);
break;
}
case R.id.copy: {
onCopy(holder);
onCopy(selection);
break;
}
case R.id.send_alternate: {
@ -1800,7 +1730,7 @@ public class MessageList
if (holder == null) {
Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " + message.getUid());
} else {
removeMessage(holder);
removeMessages(Collections.singletonList(holder));
}
}
@ -1840,7 +1770,7 @@ public class MessageList
public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {
MessageInfoHolder holder = getMessage(message);
if (holder != null) {
removeMessage(holder);
removeMessages(Collections.singletonList(holder));
}
}
@ -1914,22 +1844,18 @@ public class MessageList
mSelectedCount--;
toggleBatchButtons();
}
mAdapter.removeMessage(holder);
mAdapter.removeMessages(Collections.singletonList(holder));
}
}
}
}
/**
* @param holders
* Never {@code null}.
*/
public void removeMessages(List<MessageInfoHolder> holders) {
if (holders != null) {
mHandler.removeMessage(holders);
}
}
public void removeMessage(MessageInfoHolder holder) {
List<MessageInfoHolder> messages = new ArrayList<MessageInfoHolder>();
messages.add(holder);
removeMessages(messages);
mHandler.removeMessages(holders);
}
private void addOrUpdateMessage(Account account, String folderName, Message message, boolean verifyAgainstSearch) {
@ -2558,171 +2484,291 @@ public class MessageList
toggleBatchButtons();
}
private void setSelected(MessageInfoHolder holder, boolean newState) {
if (holder.selected != newState) {
holder.selected = newState;
mSelectedCount += (newState ? 1 : -1);
private void setSelected(final List<MessageInfoHolder> holders, final boolean newState) {
for (final MessageInfoHolder holder : holders) {
if (holder.selected != newState) {
holder.selected = newState;
mSelectedCount += (newState ? 1 : -1);
}
}
mAdapter.notifyDataSetChanged();
toggleBatchButtons();
}
private void flagSelected(Flag flag, boolean newState) {
List<Message> messageList = new ArrayList<Message>();
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
messageList.add(holder.message);
if (flag == Flag.SEEN) {
holder.read = newState;
} else if (flag == Flag.FLAGGED) {
holder.flagged = newState;
}
}
/**
* @param holders
* Messages to update. Never {@code null}.
* @param flag
* Flag to be updated on the specified messages. Never
* {@code null}.
* @param newState
* State to set for the given flag.
*/
private void setFlag(final List<MessageInfoHolder> holders, final Flag flag, final boolean newState) {
if (holders.isEmpty()) {
return;
}
final Message[] messageList = new Message[holders.size()];
int i = 0;
for (final Iterator<MessageInfoHolder> iterator = holders.iterator(); iterator.hasNext(); i++) {
final MessageInfoHolder holder = iterator.next();
messageList[i] = holder.message;
if (flag == Flag.SEEN) {
holder.read = newState;
} else if (flag == Flag.FLAGGED) {
holder.flagged = newState;
}
}
mController.setFlag(messageList.toArray(EMPTY_MESSAGE_ARRAY), flag, newState);
mController.setFlag(messageList, flag, newState);
mHandler.sortMessages();
}
private void deleteSelected() {
List<Message> messageList = new ArrayList<Message>();
List<MessageInfoHolder> removeHolderList = new ArrayList<MessageInfoHolder>();
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
removeHolderList.add(holder);
messageList.add(holder.message);
}
}
}
mAdapter.removeMessages(removeHolderList);
mController.deleteMessages(messageList.toArray(EMPTY_MESSAGE_ARRAY), null);
mSelectedCount = 0;
toggleBatchButtons();
}
private void onMoveBatch() {
if (!mController.isMoveCapable(mAccount)) {
/**
* Display the message move activity.
*
* @param holders
* Never {@code null}.
*/
private void onMove(final List<MessageInfoHolder> holders) {
if (!checkCopyOrMovePossible(holders, FolderOperation.MOVE)) {
return;
}
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
Message message = holder.message;
if (!mController.isMoveCapable(message)) {
Toast toast = Toast.makeText(this,
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
}
}
final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, holders);
}
/**
* Display the message copy activity.
*
* @param holders
* Never {@code null}.
*/
private void onCopy(final List<MessageInfoHolder> holders) {
if (!checkCopyOrMovePossible(holders, FolderOperation.COPY)) {
return;
}
final Folder folder = mCurrentFolder.folder;
final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_COPY, folder, holders);
}
/**
* Helper method to manage the invocation of
* {@link #startActivityForResult(Intent, int)} for a folder operation
* ({@link ChooseFolder} activity), while saving a list of associated
* messages.
*
* @param requestCode
* If >= 0, this code will be returned in onActivityResult() when
* the activity exits.
* @param folder
* Never {@code null}.
* @param holders
* Messages to be affected by the folder operation. Never
* {@code null}.
* @see #startActivityForResult(Intent, int)
*/
private void displayFolderChoice(final int requestCode, final Folder folder, final List<MessageInfoHolder> holders) {
final Intent intent = new Intent(this, ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid());
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, folder.getAccount().getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName());
startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_MOVE_BATCH);
// remember the selected messages for #onActivityResult
mActiveMessages = holders;
startActivityForResult(intent, requestCode);
}
private void onMoveChosenBatch(String folderName) {
if (!mController.isMoveCapable(mAccount)) {
return;
}
List<Message> messageList = new ArrayList<Message>();
List<MessageInfoHolder> removeHolderList = new ArrayList<MessageInfoHolder>();
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
Message message = holder.message;
if (!mController.isMoveCapable(message)) {
Toast toast = Toast.makeText(this,
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
messageList.add(holder.message);
removeHolderList.add(holder);
}
}
}
mAdapter.removeMessages(removeHolderList);
mController.moveMessages(mAccount, mCurrentFolder.name, messageList.toArray(EMPTY_MESSAGE_ARRAY), folderName, null);
mSelectedCount = 0;
toggleBatchButtons();
}
private void onArchiveBatch() {
String folderName = mAccount.getArchiveFolderName();
/**
* @param holders
* Never {@code null}.
*/
private void onArchive(final List<MessageInfoHolder> holders) {
final String folderName = holders.get(0).message.getFolder().getAccount().getArchiveFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
onMoveChosenBatch(folderName);
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Archive folder name)
move(holders, folderName);
}
private void onSpamBatch() {
String folderName = mAccount.getSpamFolderName();
/**
* @param holders
* Never {@code null}.
*/
private void onSpam(final List<MessageInfoHolder> holders) {
if (K9.confirmSpam()) {
// remember the message selection for #onCreateDialog(int)
mActiveMessages = holders;
showDialog(R.id.dialog_confirm_spam);
} else {
onSpamConfirmed(holders);
}
}
/**
* @param holders
* Never {@code null}.
*/
private void onSpamConfirmed(final List<MessageInfoHolder> holders) {
final String folderName = holders.get(0).message.getFolder().getAccount().getSpamFolderName();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return;
}
onMoveChosenBatch(folderName);
// TODO one should separate messages by account and call move afterwards
// (because each account might have a specific Spam folder name)
move(holders, folderName);
}
private void onCopyBatch() {
if (!mController.isCopyCapable(mAccount)) {
return;
}
private static enum FolderOperation {
COPY, MOVE
}
/**
* Display an Toast message if any message isn't synchronized
*
* @param holders
* Never <code>null</code>.
* @param move
* <code>true</code> to check move availability,
* <code>false</code> to check the copy availability
*
* @return <code>true</code> if operation is possible
*/
private boolean checkCopyOrMovePossible(final List<MessageInfoHolder> holders, final FolderOperation operation) {
if (holders.isEmpty()) {
return false;
}
boolean first = true;
for (final MessageInfoHolder holder : holders) {
final Message message = holder.message;
if (first) {
first = false;
// account check
final Account account = message.getFolder().getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
return false;
}
}
// message check
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
final Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message,
Toast.LENGTH_LONG);
toast.show();
return false;
}
}
return true;
}
/**
* Helper method to get a List of message ready to be processed. This implementation will return a list containing the sole argument.
*
* @param holder Never {@code null}.
* @return Never {@code null}.
*/
private List<MessageInfoHolder> getSelectionFromMessage(final MessageInfoHolder holder) {
final List<MessageInfoHolder> selection = Collections.singletonList(holder);
return selection;
}
/**
* Helper method to get a List of message ready to be processed. This implementation will iterate over messages and choose the checked ones.
*
* @return Never {@code null}.
*/
private List<MessageInfoHolder> getSelectionFromCheckboxes() {
final List<MessageInfoHolder> selection = new ArrayList<MessageInfoHolder>();
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
for (final MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
Message message = holder.message;
if (!mController.isCopyCapable(message)) {
Toast toast = Toast.makeText(this,
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
selection.add(holder);
}
}
}
final Folder folder = mCurrentFolder.folder;
final Intent intent = new Intent(this, ChooseFolder.class);
intent.putExtra(ChooseFolder.EXTRA_ACCOUNT, mAccount.getUuid());
intent.putExtra(ChooseFolder.EXTRA_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName());
startActivityForResult(intent, ACTIVITY_CHOOSE_FOLDER_COPY_BATCH);
return selection;
}
private void onCopyChosenBatch(String folderName) {
if (!mController.isCopyCapable(mAccount)) {
/**
* Copy the specified messages to the specified folder.
*
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
private void copy(final List<MessageInfoHolder> holders, final String destination) {
copyOrMove(holders, destination, FolderOperation.COPY);
}
/**
* Move the specified messages to the specified folder.
*
* @param holders Never {@code null}.
* @param destination Never {@code null}.
*/
private void move(final List<MessageInfoHolder> holders, final String destination) {
copyOrMove(holders, destination, FolderOperation.MOVE);
}
/**
* The underlying implementation for {@link #copy(List, String)} and
* {@link #move(List, String)}. This method was added mainly because those 2
* methods share common behavior.
*
* @param holders
* Never {@code null}.
* @param destination
* Never {@code null}.
* @param operation
* Never {@code null}.
*/
private void copyOrMove(final List<MessageInfoHolder> holders, final String destination, final FolderOperation operation) {
if (K9.FOLDER_NONE.equalsIgnoreCase(destination)) {
return;
}
List<Message> messageList = new ArrayList<Message>();
synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) {
Message message = holder.message;
if (!mController.isCopyCapable(message)) {
Toast toast = Toast.makeText(this,
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
messageList.add(holder.message);
boolean first = true;
Account account = null;
String folderName = null;
final List<Message> messages = new ArrayList<Message>(holders.size());
for (final MessageInfoHolder holder : holders) {
final Message message = holder.message;
if (first) {
first = false;
folderName = message.getFolder().getName();
account = message.getFolder().getAccount();
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(account)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(account))) {
// account is not copy/move capable
return;
}
} else if (!account.equals(message.getFolder().getAccount())
|| !folderName.equals(message.getFolder().getName())) {
// make sure all messages come from the same account/folder?
return;
}
if ((operation == FolderOperation.MOVE && !mController.isMoveCapable(message)) || (operation == FolderOperation.COPY && !mController.isCopyCapable(message))) {
final Toast toast = Toast.makeText(this, R.string.move_copy_cannot_copy_unsynced_message,
Toast.LENGTH_LONG);
toast.show();
// XXX return meaningful error value?
// message isn't synchronized
return;
}
messages.add(message);
}
if (operation == FolderOperation.MOVE) {
mController.moveMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
null);
mHandler.removeMessages(holders);
} else {
mController.copyMessages(account, folderName, messages.toArray(new Message[messages.size()]), destination,
null);
}
mController.copyMessages(mAccount, mCurrentFolder.name, messageList.toArray(EMPTY_MESSAGE_ARRAY), folderName, null);
}
protected void onAccountUnavailable() {