diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 0ed6a78bc..b42ce72a6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -123,6 +123,12 @@ android:configChanges="locale" > + + + diff --git a/res/layout/choose_account_item.xml b/res/layout/choose_account_item.xml new file mode 100644 index 000000000..d212594f0 --- /dev/null +++ b/res/layout/choose_account_item.xml @@ -0,0 +1,39 @@ + + + + + + + + + + + + diff --git a/res/layout/choose_identity_item.xml b/res/layout/choose_identity_item.xml new file mode 100644 index 000000000..0f1999003 --- /dev/null +++ b/res/layout/choose_identity_item.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + diff --git a/res/values/strings.xml b/res/values/strings.xml index 5ee4ea2e2..9a15643ef 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -667,13 +667,14 @@ Welcome to K-9 Mail setup. K-9 is an open source mail client for Android origin -- \nSent from my Android phone with K-9 Mail. Please excuse my brevity. Initial identity - Choose identity - Choose identity + Choose identity + Choose identity + Choose account/identity - Go to Account Settings -> Manage Identities to create identities - You can\'t remove your only identity - You can\'t use an identity without an email address - Your identity choice and signature changes will not be saved with a draft + Go to Account Settings -> Manage Identities to create identities + You can\'t remove your only identity + You can\'t use an identity without an email address + Your identity choice and signature changes will not be saved with a draft Earliest messages first Latest messages first diff --git a/src/com/fsck/k9/activity/ChooseAccount.java b/src/com/fsck/k9/activity/ChooseAccount.java new file mode 100644 index 000000000..e108e23b3 --- /dev/null +++ b/src/com/fsck/k9/activity/ChooseAccount.java @@ -0,0 +1,249 @@ +package com.fsck.k9.activity; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.util.TypedValue; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListAdapter; +import android.widget.ExpandableListView; +import android.widget.TextView; + +import com.fsck.k9.Account; +import com.fsck.k9.Identity; +import com.fsck.k9.K9; +import com.fsck.k9.Preferences; +import com.fsck.k9.R; + +import java.util.List; + +/** + * Activity displaying list of accounts/identity for user choice + * + * @see K9ExpandableListActivity + */ +public class ChooseAccount extends K9ExpandableListActivity +{ + + /** + * {@link Intent} extended data name for storing {@link Account#getUuid() + * account UUID} + */ + public static final String EXTRA_ACCOUNT = ChooseAccount.class.getName() + "_account"; + + /** + * {@link Intent} extended data name for storing serialized {@link Identity} + */ + public static final String EXTRA_IDENTITY = ChooseAccount.class.getName() + "_identity"; + + @Override + protected void onCreate(final Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); + setContentView(R.layout.choose_account); + + final ExpandableListView expandableListView = getExpandableListView(); + expandableListView.setItemsCanFocus(false); + + final ExpandableListAdapter adapter = createAdapter(); + setListAdapter(adapter); + + expandableListView.setOnChildClickListener(new ExpandableListView.OnChildClickListener() + { + @Override + public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, + int childPosition, long id) + { + final Identity identity = (Identity) adapter.getChild(groupPosition, childPosition); + final Account account = (Account) adapter.getGroup(groupPosition); + + final Intent intent = new Intent(); + intent.putExtra(EXTRA_ACCOUNT, account.getUuid()); + intent.putExtra(EXTRA_IDENTITY, identity); + setResult(RESULT_OK, intent); + + finish(); + return true; + } + }); + + final Bundle extras = getIntent().getExtras(); + final String uuid = extras.getString(EXTRA_ACCOUNT); + if (uuid != null) + { + final Account[] accounts = Preferences.getPreferences(this).getAccounts(); + final int length = accounts.length; + for (int i = 0; i < length; i++) + { + final Account account = accounts[i]; + if (uuid.equals(account.getUuid())) + { + // setSelectedChild() doesn't seem to obey the + // shouldExpandGroup parameter (2.1), manually expanding + // group + expandableListView.expandGroup(i); + + final List identities = account.getIdentities(); + final Identity identity = (Identity) extras.getSerializable(EXTRA_IDENTITY); + if (identity == null) + { + expandableListView.setSelectedChild(i, 0, true); + break; + } + for (int j = 0; j < identities.size(); j++) + { + final Identity loopIdentity = identities.get(j); + if (identity.equals(loopIdentity)) + { + expandableListView.setSelectedChild(i, j, true); + break; + } + } + break; + } + } + } + } + + private ExpandableListAdapter createAdapter() + { + return new IdentitiesAdapter(this, getLayoutInflater()); + } + + /** + * Dynamically provides accounts/identities data for + * {@link ExpandableListView#setAdapter(ExpandableListAdapter)}: + * + *
    + *
  • Groups represent {@link Account accounts}
  • + *
  • Children represent {@link Identity identities} of the parent account
  • + *
+ */ + public static class IdentitiesAdapter extends BaseExpandableListAdapter + { + + private Context mContext; + private LayoutInflater mLayoutInflater; + + public IdentitiesAdapter(final Context context, final LayoutInflater layoutInflater) + { + mContext = context; + mLayoutInflater = layoutInflater; + } + + @Override + public Object getChild(int groupPosition, int childPosition) + { + return getAccounts()[groupPosition].getIdentity(childPosition); + } + + @Override + public long getChildId(int groupPosition, int childPosition) + { + return Integer.valueOf(childPosition).longValue(); + } + + @Override + public int getChildrenCount(int groupPosition) + { + return getAccounts()[groupPosition].getIdentities().size(); + } + + @Override + public Object getGroup(int groupPosition) + { + return getAccounts()[groupPosition]; + } + + @Override + public int getGroupCount() + { + return getAccounts().length; + } + + @Override + public long getGroupId(int groupPosition) + { + return Integer.valueOf(groupPosition).longValue(); + } + + @Override + public View getGroupView(int groupPosition, boolean isExpanded, View convertView, + ViewGroup parent) + { + final View v; + if (convertView == null) + { + // is it okay to reuse? + v = mLayoutInflater.inflate(R.layout.choose_account_item, parent, false); + } + else + { + v = convertView; + } + + final TextView description = (TextView) v.findViewById(R.id.description); + final Account account = getAccounts()[groupPosition]; + description.setText(account.getDescription()); + description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, K9.getFontSizes().getAccountName()); + + v.findViewById(R.id.chip).setBackgroundColor(account.getChipColor()); + + return v; + } + + @Override + public View getChildView(int groupPosition, int childPosition, boolean isLastChild, + View convertView, ViewGroup parent) + { + final Account account = getAccounts()[groupPosition]; + final Identity identity = account.getIdentity(childPosition); + + final View v; + if (convertView == null) + { + v = mLayoutInflater.inflate(R.layout.choose_identity_item, parent, false); + } + else + { + v = convertView; + } + + final TextView name = (TextView) v.findViewById(R.id.name); + final TextView description = (TextView) v.findViewById(R.id.description); + name.setTextSize(TypedValue.COMPLEX_UNIT_DIP, K9.getFontSizes().getAccountName()); + description.setTextSize(TypedValue.COMPLEX_UNIT_DIP, K9.getFontSizes().getAccountDescription()); + + name.setText(identity.getDescription()); + description.setText(String.format("%s <%s>", identity.getName(), identity.getEmail())); + + v.findViewById(R.id.chip).setBackgroundColor(account.getChipColor()); + + return v; + } + + @Override + public boolean hasStableIds() + { + // returning false since accounts/identities are mutable + return false; + } + + @Override + public boolean isChildSelectable(int groupPosition, int childPosition) + { + return true; + } + + private Account[] getAccounts() + { + return Preferences.getPreferences(mContext).getAccounts(); + } + } +} diff --git a/src/com/fsck/k9/activity/K9ExpandableListActivity.java b/src/com/fsck/k9/activity/K9ExpandableListActivity.java new file mode 100644 index 000000000..c5438289a --- /dev/null +++ b/src/com/fsck/k9/activity/K9ExpandableListActivity.java @@ -0,0 +1,20 @@ +package com.fsck.k9.activity; + +import android.app.ExpandableListActivity; +import android.os.Bundle; + +import com.fsck.k9.K9; + +/** + * @see ExpandableListActivity + */ +public class K9ExpandableListActivity extends ExpandableListActivity +{ + + @Override + protected void onCreate(Bundle savedInstanceState) + { + setTheme(K9.getK9Theme()); + super.onCreate(savedInstanceState); + } +} diff --git a/src/com/fsck/k9/activity/MessageCompose.java b/src/com/fsck/k9/activity/MessageCompose.java index d9f3f370f..32787318b 100644 --- a/src/com/fsck/k9/activity/MessageCompose.java +++ b/src/com/fsck/k9/activity/MessageCompose.java @@ -56,8 +56,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc private static final String EXTRA_ACCOUNT = "account"; - private static final String EXTRA_FOLDER = "folder"; - private static final String EXTRA_MESSAGE = "message"; + private static final String EXTRA_MESSAGE_REFERENCE = "message_reference"; private static final String STATE_KEY_ATTACHMENTS = "com.fsck.k9.activity.MessageCompose.attachments"; @@ -87,14 +86,30 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc private static final int ACTIVITY_REQUEST_PICK_ATTACHMENT = 1; private static final int ACTIVITY_CHOOSE_IDENTITY = 2; + private static final int ACTIVITY_CHOOSE_ACCOUNT = 3; + /** + * The account used for message composition. + */ private Account mAccount; + + /** + * This identity's settings are used for message composition. + * Note: This has to be an identity of the account {@link #mAccount}. + */ private Identity mIdentity; + private boolean mIdentityChanged = false; private boolean mSignatureChanged = false; - private String mFolder; - private String mSourceMessageUid; + + /** + * Reference to the source message (in case of reply, forward, or edit + * draft actions). + */ + private MessageReference mMessageReference; + private Message mSourceMessage; + /** * Indicates that the source message has been processed at least once and should not * be processed on any subsequent loads. This protects us from adding attachments that @@ -212,9 +227,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc boolean replyAll) { Intent i = new Intent(context, MessageCompose.class); - i.putExtra(EXTRA_ACCOUNT, account.getUuid()); - i.putExtra(EXTRA_FOLDER, message.getFolder().getName()); - i.putExtra(EXTRA_MESSAGE, message.getUid()); + i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference()); if (replyAll) { i.setAction(ACTION_REPLY_ALL); @@ -235,9 +248,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc public static void actionForward(Context context, Account account, Message message) { Intent i = new Intent(context, MessageCompose.class); - i.putExtra(EXTRA_ACCOUNT, account.getUuid()); - i.putExtra(EXTRA_FOLDER, message.getFolder().getName()); - i.putExtra(EXTRA_MESSAGE, message.getUid()); + i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference()); i.setAction(ACTION_FORWARD); context.startActivity(i); } @@ -255,9 +266,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc public static void actionEditDraft(Context context, Account account, Message message) { Intent i = new Intent(context, MessageCompose.class); - i.putExtra(EXTRA_ACCOUNT, account.getUuid()); - i.putExtra(EXTRA_FOLDER, message.getFolder().getName()); - i.putExtra(EXTRA_MESSAGE, message.getUid()); + i.putExtra(EXTRA_MESSAGE_REFERENCE, message.makeMessageReference()); i.setAction(ACTION_EDIT_DRAFT); context.startActivity(i); } @@ -266,19 +275,24 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - setContentView(R.layout.message_compose); - Intent intent = getIntent(); - String accountUuid = intent.getStringExtra(EXTRA_ACCOUNT); + final Intent intent = getIntent(); + + mMessageReference = (MessageReference) intent.getSerializableExtra(EXTRA_MESSAGE_REFERENCE); + + final String accountUuid = (mMessageReference != null) ? + mMessageReference.accountUuid : + intent.getStringExtra(EXTRA_ACCOUNT); + mAccount = Preferences.getPreferences(this).getAccount(accountUuid); if (mAccount == null) { mAccount = Preferences.getPreferences(this).getDefaultAccount(); } + if (mAccount == null) { /* @@ -291,7 +305,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc return; } - mAddressAdapter = EmailAddressAdapter.getInstance(this); mAddressValidator = new EmailAddressValidator(); @@ -512,11 +525,6 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc mBccView.setText(addressList); } - else - { - mFolder = (String) intent.getStringExtra(EXTRA_FOLDER); - mSourceMessageUid = (String) intent.getStringExtra(EXTRA_MESSAGE); - } if (mIdentity == null) { @@ -545,7 +553,10 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc updateFrom(); updateSignature(); - if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action) || ACTION_FORWARD.equals(action) || ACTION_EDIT_DRAFT.equals(action)) + if (ACTION_REPLY.equals(action) || + ACTION_REPLY_ALL.equals(action) || + ACTION_FORWARD.equals(action) || + ACTION_EDIT_DRAFT.equals(action)) { /* * If we need to load the message we add ourself as a message listener here @@ -554,34 +565,48 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc * There is no harm in adding twice. */ MessagingController.getInstance(getApplication()).addListener(mListener); - MessagingController.getInstance(getApplication()).loadMessageForView(mAccount, mFolder, mSourceMessageUid, null); + + final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid); + final String folderName = mMessageReference.folderName; + final String sourceMessageUid = mMessageReference.uid; + MessagingController.getInstance(getApplication()).loadMessageForView(account, folderName, sourceMessageUid, null); } if (!ACTION_EDIT_DRAFT.equals(action)) { String bccAddress = mAccount.getAlwaysBcc(); - if (bccAddress!=null - && !"".equals(bccAddress)) + if ((bccAddress != null) && !("".equals(bccAddress))) { - addAddress(mBccView, new Address(mAccount.getAlwaysBcc(), "")); + addAddress(mBccView, new Address(bccAddress, "")); } } + /* if (K9.DEBUG) - Log.d(K9.LOG_TAG, "action = " + action + ", mAccount = " + mAccount + ", mFolder = " + mFolder + ", mSourceMessageUid = " + mSourceMessageUid); - if ((ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action)) && mAccount != null && mFolder != null && mSourceMessageUid != null) + Log.d(K9.LOG_TAG, "action = " + action + ", account = " + mMessageReference.accountUuid + ", folder = " + mMessageReference.folderName + ", sourceMessageUid = " + mMessageReference.uid); + */ + + if (ACTION_REPLY.equals(action) || + ACTION_REPLY_ALL.equals(action)) { if (K9.DEBUG) Log.d(K9.LOG_TAG, "Setting message ANSWERED flag to true"); + // TODO: Really, we should wait until we send the message, but that would require saving the original // message info along with a Draft copy, in case it is left in Drafts for a while before being sent - MessagingController.getInstance(getApplication()).setFlag(mAccount, mFolder, new String[] { mSourceMessageUid }, Flag.ANSWERED, true); + + final Account account = Preferences.getPreferences(this).getAccount(mMessageReference.accountUuid); + final String folderName = mMessageReference.folderName; + final String sourceMessageUid = mMessageReference.uid; + MessagingController.getInstance(getApplication()).setFlag(account, folderName, new String[] { sourceMessageUid }, Flag.ANSWERED, true); } updateTitle(); } - if (ACTION_REPLY.equals(action) || ACTION_REPLY_ALL.equals(action) || ACTION_EDIT_DRAFT.equals(action)) + if (ACTION_REPLY.equals(action) || + ACTION_REPLY_ALL.equals(action) || + ACTION_EDIT_DRAFT.equals(action)) { //change focus to message body. mMessageContentView.requestFocus(); @@ -873,7 +898,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc * We're saving a previously saved draft, so update the new message's uid * to the old message's uid. */ - message.setUid(mSourceMessageUid); + message.setUid(mMessageReference.uid); } String k9identity = Utility.base64Encode("" + mMessageContentView.getText().toString().length()); @@ -1103,9 +1128,72 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc case ACTIVITY_CHOOSE_IDENTITY: onIdentityChosen(data); break; + case ACTIVITY_CHOOSE_ACCOUNT: + onAccountChosen(data); + break; } } + private void onAccountChosen(final Intent intent) + { + final Bundle extras = intent.getExtras(); + final String uuid = extras.getString(ChooseAccount.EXTRA_ACCOUNT); + final Identity identity = (Identity) extras.getSerializable(ChooseAccount.EXTRA_IDENTITY); + + final Account account = Preferences.getPreferences(this).getAccount(uuid); + + if (!mAccount.equals(account)) + { + if (K9.DEBUG) + { + Log.v(K9.LOG_TAG, "Switching account from " + mAccount + " to " + account); + } + + // on draft edit, make sure we don't keep previous message UID + if (ACTION_EDIT_DRAFT.equals(getIntent().getAction())) + { + mMessageReference = null; + } + + // test whether there is something to save + if (mDraftNeedsSaving || (mDraftUid != null)) + { + final String previousDraftUid = mDraftUid; + final Account previousAccount = mAccount; + + // make current message appear as new + mDraftUid = null; + + // actual account switch + mAccount = account; + + if (K9.DEBUG) + { + Log.v(K9.LOG_TAG, "Account switch, saving new draft in new account"); + } + sendOrSaveMessage(true); + + if (previousDraftUid != null) + { + if (K9.DEBUG) + { + Log.v(K9.LOG_TAG, "Account switch, deleting draft from previous account: " + + previousDraftUid); + } + MessagingController.getInstance(getApplication()).deleteDraft(previousAccount, + previousDraftUid); + } + } + else + { + mAccount = account; + } + // not sure how to handle mFolder, mSourceMessage? + } + + switchToIdentity(identity); + } + private void onIdentityChosen(Intent intent) { Bundle bundle = intent.getExtras();; @@ -1201,7 +1289,16 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc private void onChooseIdentity() { - if (mAccount.getIdentities().size() > 1) + // keep things simple: trigger account choice only if there are more + // than 1 account + if (Preferences.getPreferences(this).getAccounts().length > 1) + { + final Intent intent = new Intent(this, ChooseAccount.class); + intent.putExtra(ChooseAccount.EXTRA_ACCOUNT, mAccount.getUuid()); + intent.putExtra(ChooseAccount.EXTRA_IDENTITY, mIdentity); + startActivityForResult(intent, ACTIVITY_CHOOSE_ACCOUNT); + } + else if (mAccount.getIdentities().size() > 1) { Intent intent = new Intent(this, ChooseIdentity.class); intent.putExtra(ChooseIdentity.EXTRA_ACCOUNT, mAccount.getUuid()); @@ -1702,8 +1799,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc @Override public void loadMessageForViewStarted(Account account, String folder, String uid) { - if (mSourceMessageUid==null - || !mSourceMessageUid.equals(uid)) + if ((mMessageReference == null) || !mMessageReference.uid.equals(uid)) { return; } @@ -1714,8 +1810,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc @Override public void loadMessageForViewFinished(Account account, String folder, String uid, Message message) { - if (mSourceMessageUid==null - || !mSourceMessageUid.equals(uid)) + if ((mMessageReference == null) || !mMessageReference.uid.equals(uid)) { return; } @@ -1726,8 +1821,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc @Override public void loadMessageForViewBodyAvailable(Account account, String folder, String uid, final Message message) { - if (mSourceMessageUid==null - || !mSourceMessageUid.equals(uid)) + if ((mMessageReference == null) || !mMessageReference.uid.equals(uid)) { return; } @@ -1745,8 +1839,7 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc @Override public void loadMessageForViewFailed(Account account, String folder, String uid, Throwable t) { - if (mSourceMessageUid==null - || !mSourceMessageUid.equals(uid)) + if ((mMessageReference == null) || !mMessageReference.uid.equals(uid)) { return; } @@ -1757,23 +1850,32 @@ public class MessageCompose extends K9Activity implements OnClickListener, OnFoc @Override public void messageUidChanged(Account account, String folder, String oldUid, String newUid) { - if (account.equals(mAccount) && folder.equals(mAccount.getDraftsFolderName())) + //TODO: is this really necessary here? mDraftUid is update after the call to MessagingController.saveDraft() + // Track UID changes of the draft message + if (account.equals(mAccount) && + folder.equals(mAccount.getDraftsFolderName()) && + oldUid.equals(mDraftUid)) { - if (oldUid.equals(mDraftUid)) - { - mDraftUid = newUid; - } + mDraftUid = newUid; } - if (account.equals(mAccount) && (folder.equals(mFolder))) + // Track UID changes of the source message + if (mMessageReference != null) { - if (oldUid.equals(mSourceMessageUid)) + final Account sourceAccount = Preferences.getPreferences(MessageCompose.this).getAccount(mMessageReference.accountUuid); + final String sourceFolder = mMessageReference.folderName; + final String sourceMessageUid = mMessageReference.uid; + + if (account.equals(sourceAccount) && (folder.equals(sourceFolder))) { - mSourceMessageUid = newUid; - } - if (mSourceMessage != null && (oldUid.equals(mSourceMessage.getUid()))) - { - mSourceMessage.setUid(newUid); + if (oldUid.equals(sourceMessageUid)) + { + mMessageReference.uid = newUid; + } + if ((mSourceMessage != null) && (oldUid.equals(mSourceMessage.getUid()))) + { + mSourceMessage.setUid(newUid); + } } } }