1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-08-13 17:03:48 -04:00

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

View File

@ -22,33 +22,33 @@ import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; 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;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.GestureDetector; import android.view.GestureDetector;
import android.view.GestureDetector.SimpleOnGestureListener;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.MotionEvent; import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.Window; import android.view.Window;
import android.view.ContextMenu.ContextMenuInfo; import android.view.animation.Animation;
import android.view.GestureDetector.SimpleOnGestureListener; import android.view.animation.Animation.AnimationListener;
import android.view.View.OnClickListener; import android.view.animation.AnimationUtils;
import android.widget.AdapterView; import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.BaseAdapter; import android.widget.BaseAdapter;
import android.widget.CheckBox; import android.widget.CheckBox;
import android.widget.CompoundButton; import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageButton; import android.widget.ImageButton;
import android.widget.ListView; import android.widget.ListView;
import android.widget.ProgressBar; import android.widget.ProgressBar;
import android.widget.TextView; import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.CompoundButton.OnCheckedChangeListener;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.AccountStats; 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.FolderSettings;
import com.fsck.k9.activity.setup.Prefs; import com.fsck.k9.activity.setup.Prefs;
import com.fsck.k9.controller.MessagingController; 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.MessagingController.SORT_TYPE;
import com.fsck.k9.controller.MessagingListener;
import com.fsck.k9.helper.MessageHelper; import com.fsck.k9.helper.MessageHelper;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Flag; import com.fsck.k9.mail.Flag;
import com.fsck.k9.mail.Folder; import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.Message; import com.fsck.k9.mail.Message;
import com.fsck.k9.mail.store.LocalStore; 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.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_MOVE = 1;
private static final int ACTIVITY_CHOOSE_FOLDER_COPY = 2; 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_ACCOUNT = "account";
private static final String EXTRA_FOLDER = "folder"; private static final String EXTRA_FOLDER = "folder";
@ -298,9 +296,21 @@ public class MessageList
private FontSizes mFontSizes = K9.getFontSizes(); private FontSizes mFontSizes = K9.getFontSizes();
private Bundle mState = null; 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 */ /* package visibility for faster inner class access */
MessageHelper mMessageHelper = MessageHelper.getInstance(this); MessageHelper mMessageHelper = MessageHelper.getInstance(this);
@ -327,7 +337,13 @@ public class MessageList
} }
class MessageListHandler { 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() { runOnUiThread(new Runnable() {
public void run() { public void run() {
for (MessageInfoHolder message : messages) { for (MessageInfoHolder message : messages) {
@ -348,7 +364,13 @@ public class MessageList
}); });
} }
/**
* @param messages Never {@code null}.
*/
public void addMessages(final List<MessageInfoHolder> messages) { public void addMessages(final List<MessageInfoHolder> messages) {
if (messages.isEmpty()) {
return;
}
final boolean wasEmpty = mAdapter.messages.isEmpty(); final boolean wasEmpty = mAdapter.messages.isEmpty();
runOnUiThread(new Runnable() { runOnUiThread(new Runnable() {
public void run() { public void run() {
@ -577,7 +599,7 @@ public class MessageList
if (mSelectedCount > 0) { if (mSelectedCount > 0) {
// In multiselect mode make sure that clicking on the item results // In multiselect mode make sure that clicking on the item results
// in toggling the 'selected' checkbox. // in toggling the 'selected' checkbox.
setSelected(message, !message.selected); setSelected(Collections.singletonList(message), !message.selected);
} else { } else {
onOpenMessage(message); onOpenMessage(message);
} }
@ -913,18 +935,20 @@ public class MessageList
if (position >= 0) { if (position >= 0) {
MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position); MessageInfoHolder message = (MessageInfoHolder) mAdapter.getItem(position);
final List<MessageInfoHolder> selection = getSelectionFromMessage(message);
if (message != null) { if (message != null) {
switch (keyCode) { switch (keyCode) {
case KeyEvent.KEYCODE_DEL: { case KeyEvent.KEYCODE_DEL: {
onDelete(message, position); onDelete(selection);
return true; return true;
} }
case KeyEvent.KEYCODE_S: { case KeyEvent.KEYCODE_S: {
setSelected(message, !message.selected); setSelected(selection, !message.selected);
return true; return true;
} }
case KeyEvent.KEYCODE_D: { case KeyEvent.KEYCODE_D: {
onDelete(message, position); onDelete(selection);
return true; return true;
} }
case KeyEvent.KEYCODE_F: { case KeyEvent.KEYCODE_F: {
@ -940,23 +964,23 @@ public class MessageList
return true; return true;
} }
case KeyEvent.KEYCODE_G: { case KeyEvent.KEYCODE_G: {
onToggleFlag(message); setFlag(selection, Flag.FLAGGED, !message.flagged);
return true; return true;
} }
case KeyEvent.KEYCODE_M: { case KeyEvent.KEYCODE_M: {
onMove(message); onMove(selection);
return true; return true;
} }
case KeyEvent.KEYCODE_V: { case KeyEvent.KEYCODE_V: {
onArchive(message); onArchive(selection);
return true; return true;
} }
case KeyEvent.KEYCODE_Y: { case KeyEvent.KEYCODE_Y: {
onCopy(message); onCopy(selection);
return true; return true;
} }
case KeyEvent.KEYCODE_Z: { case KeyEvent.KEYCODE_Z: {
onToggleRead(message); setFlag(selection, Flag.SEEN, !message.read);
return true; return true;
} }
} }
@ -1097,150 +1121,57 @@ public class MessageList
reSort(); reSort();
} }
private void onDelete(MessageInfoHolder holder, int position) { /**
mAdapter.removeMessage(holder); * @param holders
mController.deleteMessages(new Message[] { holder.message }, null); * Never {@code null}.
} */
private void onDelete(final List<MessageInfoHolder> holders) {
private void onMove(MessageInfoHolder holder) { // FIXME: removeMessage does it own check on the 'selected' field, we're duplicating the logic here...
if (!mController.isMoveCapable(holder.message.getFolder().getAccount())) { final List<Message> messagesToRemove = new ArrayList<Message>();
return; for (MessageInfoHolder holder : holders) {
if (holder.selected) {
messagesToRemove.add(holder.message);
}
} }
mHandler.removeMessages(holders);
if (!mController.isMoveCapable(holder.message)) { mController.deleteMessages(messagesToRemove.toArray(EMPTY_MESSAGE_ARRAY), null);
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);
} }
@Override @Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) { protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode != RESULT_OK) if (resultCode != RESULT_OK) {
return; return;
}
switch (requestCode) { switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE: case ACTIVITY_CHOOSE_FOLDER_MOVE:
case ACTIVITY_CHOOSE_FOLDER_COPY: { case ACTIVITY_CHOOSE_FOLDER_COPY: {
if (data == null) if (data == null) {
return; return;
}
final String destFolderName = data.getStringExtra(ChooseFolder.EXTRA_NEW_FOLDER); 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)) { if (destFolderName != null) {
final Account account = m.message.getFolder().getAccount(); 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); account.setLastSelectedFolderName(destFolderName);
switch (requestCode) { switch (requestCode) {
case ACTIVITY_CHOOSE_FOLDER_MOVE: case ACTIVITY_CHOOSE_FOLDER_MOVE:
onMoveChosen(m, destFolderName); move(holders, destFolderName);
break; break;
case ACTIVITY_CHOOSE_FOLDER_COPY: case ACTIVITY_CHOOSE_FOLDER_COPY:
onCopyChosen(m, destFolderName); copy(holders, destFolderName);
break; break;
} }
} }
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() { new Runnable() {
@Override @Override
public void run() { public void run() {
moveToSpamFolder(mSelectedMessage); onSpamConfirmed(mActiveMessages);
// No further need for this reference // No further need for this reference
mSelectedMessage = null; mActiveMessages = null;
} }
}); });
} }
@ -1353,6 +1284,7 @@ public class MessageList
@Override @Override
public boolean onOptionsItemSelected(MenuItem item) { public boolean onOptionsItemSelected(MenuItem item) {
final List<MessageInfoHolder> selection = getSelectionFromCheckboxes();
int itemId = item.getItemId(); int itemId = item.getItemId();
switch (itemId) { switch (itemId) {
case R.id.compose: { case R.id.compose: {
@ -1399,23 +1331,23 @@ public class MessageList
return true; return true;
} }
case R.id.batch_delete_op: { case R.id.batch_delete_op: {
deleteSelected(); onDelete(selection);
return true; return true;
} }
case R.id.batch_mark_read_op: { case R.id.batch_mark_read_op: {
flagSelected(Flag.SEEN, true); setFlag(selection, Flag.SEEN, true);
return true; return true;
} }
case R.id.batch_mark_unread_op: { case R.id.batch_mark_unread_op: {
flagSelected(Flag.SEEN, false); setFlag(selection, Flag.SEEN, false);
return true; return true;
} }
case R.id.batch_flag_op: { case R.id.batch_flag_op: {
flagSelected(Flag.FLAGGED, true); setFlag(selection, Flag.FLAGGED, true);
return true; return true;
} }
case R.id.batch_unflag_op: { case R.id.batch_unflag_op: {
flagSelected(Flag.FLAGGED, false); setFlag(selection, Flag.FLAGGED, false);
return true; return true;
} }
case R.id.app_settings: { case R.id.app_settings: {
@ -1462,19 +1394,19 @@ public class MessageList
return true; return true;
} }
case R.id.batch_copy_op: { case R.id.batch_copy_op: {
onCopyBatch(); onCopy(selection);
return true; return true;
} }
case R.id.batch_archive_op: { case R.id.batch_archive_op: {
onArchiveBatch(); onArchive(selection);
return true; return true;
} }
case R.id.batch_spam_op: { case R.id.batch_spam_op: {
onSpamBatch(); onSpam(selection);
return true; return true;
} }
case R.id.batch_move_op: { case R.id.batch_move_op: {
onMoveBatch(); onMove(selection);
return true; return true;
} }
case R.id.expunge: { case R.id.expunge: {
@ -1564,28 +1496,26 @@ public class MessageList
@Override @Override
public boolean onContextItemSelected(MenuItem item) { public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
MessageInfoHolder holder = mSelectedMessage; final MessageInfoHolder holder = mSelectedMessage == null ? (MessageInfoHolder) mAdapter.getItem(info.position) : mSelectedMessage;
// don't need this anymore // don't need this anymore
mSelectedMessage = null; mSelectedMessage = null;
if (holder == null) {
holder = (MessageInfoHolder) mAdapter.getItem(info.position);
}
final List<MessageInfoHolder> selection = getSelectionFromMessage(holder);
switch (item.getItemId()) { switch (item.getItemId()) {
case R.id.open: { case R.id.open: {
onOpenMessage(holder); onOpenMessage(holder);
break; break;
} }
case R.id.select: { case R.id.select: {
setSelected(holder, true); setSelected(selection, true);
break; break;
} }
case R.id.deselect: { case R.id.deselect: {
setSelected(holder, false); setSelected(selection, false);
break; break;
} }
case R.id.delete: { case R.id.delete: {
onDelete(holder, info.position); onDelete(selection);
break; break;
} }
case R.id.reply: { case R.id.reply: {
@ -1614,19 +1544,19 @@ public class MessageList
break; break;
} }
case R.id.archive: { case R.id.archive: {
onArchive(holder); onArchive(selection);
break; break;
} }
case R.id.spam: { case R.id.spam: {
onSpam(holder); onSpam(selection);
break; break;
} }
case R.id.move: { case R.id.move: {
onMove(holder); onMove(selection);
break; break;
} }
case R.id.copy: { case R.id.copy: {
onCopy(holder); onCopy(selection);
break; break;
} }
case R.id.send_alternate: { case R.id.send_alternate: {
@ -1800,7 +1730,7 @@ public class MessageList
if (holder == null) { if (holder == null) {
Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " + message.getUid()); Log.w(K9.LOG_TAG, "Got callback to remove non-existent message with UID " + message.getUid());
} else { } else {
removeMessage(holder); removeMessages(Collections.singletonList(holder));
} }
} }
@ -1840,7 +1770,7 @@ public class MessageList
public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) { public void listLocalMessagesRemoveMessage(Account account, String folder, Message message) {
MessageInfoHolder holder = getMessage(message); MessageInfoHolder holder = getMessage(message);
if (holder != null) { if (holder != null) {
removeMessage(holder); removeMessages(Collections.singletonList(holder));
} }
} }
@ -1914,22 +1844,18 @@ public class MessageList
mSelectedCount--; mSelectedCount--;
toggleBatchButtons(); toggleBatchButtons();
} }
mAdapter.removeMessage(holder); mAdapter.removeMessages(Collections.singletonList(holder));
} }
} }
} }
} }
/**
* @param holders
* Never {@code null}.
*/
public void removeMessages(List<MessageInfoHolder> holders) { public void removeMessages(List<MessageInfoHolder> holders) {
if (holders != null) { mHandler.removeMessages(holders);
mHandler.removeMessage(holders);
}
}
public void removeMessage(MessageInfoHolder holder) {
List<MessageInfoHolder> messages = new ArrayList<MessageInfoHolder>();
messages.add(holder);
removeMessages(messages);
} }
private void addOrUpdateMessage(Account account, String folderName, Message message, boolean verifyAgainstSearch) { private void addOrUpdateMessage(Account account, String folderName, Message message, boolean verifyAgainstSearch) {
@ -2558,171 +2484,291 @@ public class MessageList
toggleBatchButtons(); toggleBatchButtons();
} }
private void setSelected(MessageInfoHolder holder, boolean newState) { private void setSelected(final List<MessageInfoHolder> holders, final boolean newState) {
if (holder.selected != newState) { for (final MessageInfoHolder holder : holders) {
holder.selected = newState; if (holder.selected != newState) {
mSelectedCount += (newState ? 1 : -1); holder.selected = newState;
mSelectedCount += (newState ? 1 : -1);
}
} }
mAdapter.notifyDataSetChanged(); mAdapter.notifyDataSetChanged();
toggleBatchButtons(); toggleBatchButtons();
} }
private void flagSelected(Flag flag, boolean newState) { /**
List<Message> messageList = new ArrayList<Message>(); * @param holders
synchronized (mAdapter.messages) { * Messages to update. Never {@code null}.
for (MessageInfoHolder holder : mAdapter.messages) { * @param flag
if (holder.selected) { * Flag to be updated on the specified messages. Never
messageList.add(holder.message); * {@code null}.
if (flag == Flag.SEEN) { * @param newState
holder.read = newState; * State to set for the given flag.
} else if (flag == Flag.FLAGGED) { */
holder.flagged = newState; 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(); mHandler.sortMessages();
} }
private void deleteSelected() { /**
List<Message> messageList = new ArrayList<Message>(); * Display the message move activity.
List<MessageInfoHolder> removeHolderList = new ArrayList<MessageInfoHolder>(); *
synchronized (mAdapter.messages) { * @param holders
for (MessageInfoHolder holder : mAdapter.messages) { * Never {@code null}.
if (holder.selected) { */
removeHolderList.add(holder); private void onMove(final List<MessageInfoHolder> holders) {
messageList.add(holder.message); if (!checkCopyOrMovePossible(holders, FolderOperation.MOVE)) {
}
}
}
mAdapter.removeMessages(removeHolderList);
mController.deleteMessages(messageList.toArray(EMPTY_MESSAGE_ARRAY), null);
mSelectedCount = 0;
toggleBatchButtons();
}
private void onMoveBatch() {
if (!mController.isMoveCapable(mAccount)) {
return; return;
} }
synchronized (mAdapter.messages) { final Folder folder = holders.size() == 1 ? holders.get(0).message.getFolder() : mCurrentFolder.folder;
for (MessageInfoHolder holder : mAdapter.messages) { displayFolderChoice(ACTIVITY_CHOOSE_FOLDER_MOVE, folder, holders);
if (holder.selected) { }
Message message = holder.message;
if (!mController.isMoveCapable(message)) { /**
Toast toast = Toast.makeText(this, * Display the message copy activity.
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); *
toast.show(); * @param holders
return; * 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); 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_CUR_FOLDER, folder.getName());
intent.putExtra(ChooseFolder.EXTRA_SEL_FOLDER, folder.getAccount().getLastSelectedFolderName()); 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)) { * @param holders
return; * Never {@code null}.
} */
List<Message> messageList = new ArrayList<Message>(); private void onArchive(final List<MessageInfoHolder> holders) {
final String folderName = holders.get(0).message.getFolder().getAccount().getArchiveFolderName();
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();
if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) { if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return; 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)) { if (K9.FOLDER_NONE.equalsIgnoreCase(folderName)) {
return; 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() { private static enum FolderOperation {
if (!mController.isCopyCapable(mAccount)) { COPY, MOVE
return; }
}
/**
* 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) { synchronized (mAdapter.messages) {
for (MessageInfoHolder holder : mAdapter.messages) { for (final MessageInfoHolder holder : mAdapter.messages) {
if (holder.selected) { if (holder.selected) {
Message message = holder.message; selection.add(holder);
if (!mController.isCopyCapable(message)) {
Toast toast = Toast.makeText(this,
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG);
toast.show();
return;
}
} }
} }
} }
return selection;
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);
} }
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; return;
} }
List<Message> messageList = new ArrayList<Message>(); boolean first = true;
synchronized (mAdapter.messages) { Account account = null;
for (MessageInfoHolder holder : mAdapter.messages) { String folderName = null;
if (holder.selected) {
Message message = holder.message; final List<Message> messages = new ArrayList<Message>(holders.size());
if (!mController.isCopyCapable(message)) {
Toast toast = Toast.makeText(this, for (final MessageInfoHolder holder : holders) {
R.string.move_copy_cannot_copy_unsynced_message, Toast.LENGTH_LONG); final Message message = holder.message;
toast.show(); if (first) {
return; first = false;
} folderName = message.getFolder().getName();
messageList.add(holder.message); 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() { protected void onAccountUnavailable() {