Add "Accounts" header to the AB spinner

Change-Id: I35e4b7295db16db803ede084cf65fa3f2115f770
This commit is contained in:
Todd Kennedy 2011-06-06 14:03:40 -07:00
parent 49e424c60f
commit 69461503fb
4 changed files with 170 additions and 99 deletions

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/display_name"
android:ellipsize="end"
style="?android:attr/listSeparatorTextViewStyle" />

View File

@ -172,7 +172,6 @@
<item quantity="other"><xliff:g id="unread_message_count" example="279">%1$d</xliff:g> unread (<xliff:g id="account">%2$s</xliff:g>)</item>
</plurals>
<!-- Notification message in notifications window when 2+ accounts have new mail; e.g, "in 3 accounts". -->
<plurals name="notification_new_multi_account_fmt">
<!-- Case of "few" (small number of) accounts with unread messages. -->
@ -275,6 +274,8 @@
<!-- Label shown in the account/mailbox selector to switch to the show all the top-level
mailboxes. [CHAR LIMIT=30] -->
<string name="mailbox_list_account_selector_show_all_folders">Show all folders</string>
<!-- Account list header in the account/mailbox selector. [CHAR LIMIT=30] -->
<string name="mailbox_list_account_selector_account_header">Accounts</string>
<!-- Appears at the bottom of list of messages; user selects to load more messages from that folder. -->
<string name="message_list_load_more_messages_action">Load more messages</string>

View File

@ -16,6 +16,8 @@
package com.android.email.activity;
import com.google.common.annotations.VisibleForTesting;
import com.android.email.R;
import com.android.email.data.ClosingMatrixCursor;
import com.android.email.data.ThrottlingCursorLoader;
@ -28,47 +30,52 @@ import android.content.Context;
import android.content.Loader;
import android.database.Cursor;
import android.database.MatrixCursor;
import android.database.MatrixCursor.RowBuilder;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.CursorAdapter;
import android.widget.TextView;
/**
* Adapter for the account selector on {@link UIControllerTwoPane}.
* Account selector spinner.
*
* TODO Test it!
*/
public class AccountSelectorAdapter extends CursorAdapter {
/** Projection used to query from Account */
/** meta data column for an account's unread count */
private static final String UNREAD_COUNT = "unreadCount";
/** meta data column for the row type; used for display purposes */
private static final String ROW_TYPE = "rowType";
private static final int ROW_TYPE_HEADER = AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
@SuppressWarnings("unused")
private static final int ROW_TYPE_MAILBOX = 0;
private static final int ROW_TYPE_ACCOUNT = 1;
private static final int ITEM_VIEW_TYPE_ACCOUNT = 0;
/** Projection for account database query */
private static final String[] ACCOUNT_PROJECTION = new String[] {
EmailContent.RECORD_ID,
EmailContent.Account.DISPLAY_NAME,
EmailContent.Account.EMAIL_ADDRESS,
Account.ID,
Account.DISPLAY_NAME,
Account.EMAIL_ADDRESS,
};
/**
* Projection for the resulting MatrixCursor -- must be {@link #ACCOUNT_PROJECTION}
* with "UNREAD_COUNT".
* Projection used for the selector display; we add meta data that doesn't exist in the
* account database, so, this should be a super-set of {@link #ACCOUNT_PROJECTION}.
*/
private static final String[] RESULT_PROJECTION = new String[] {
EmailContent.RECORD_ID,
EmailContent.Account.DISPLAY_NAME,
EmailContent.Account.EMAIL_ADDRESS,
"UNREAD_COUNT"
private static final String[] ADAPTER_PROJECTION = new String[] {
ROW_TYPE,
Account.ID,
Account.DISPLAY_NAME,
Account.EMAIL_ADDRESS,
UNREAD_COUNT,
};
private static final int ID_COLUMN = 0;
private static final int DISPLAY_NAME_COLUMN = 1;
private static final int EMAIL_ADDRESS_COLUMN = 2;
private static final int UNREAD_COUNT_COLUMN = 3;
/** Sort order. Show the default account first. */
private static final String ORDER_BY =
EmailContent.Account.IS_DEFAULT + " desc, " + EmailContent.Account.RECORD_ID;
private final LayoutInflater mInflater;
@SuppressWarnings("hiding")
private final Context mContext;
public static Loader<Cursor> createLoader(Context context) {
@ -83,27 +90,37 @@ public class AccountSelectorAdapter extends CursorAdapter {
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
final View view = mInflater.inflate(R.layout.account_selector_dropdown, parent, false);
Cursor c = getCursor();
c.moveToPosition(position);
final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address);
final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count);
final String displayName = getAccountDisplayName(position);
final String emailAddress = getAccountEmailAddress(position);
displayNameView.setText(displayName);
// Show the email address only when it's different from the display name.
if (emailAddress.equals(displayName)) {
emailAddressView.setVisibility(View.GONE);
View view;
if (c.getInt(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER) {
view = mInflater.inflate(R.layout.account_selector_dropdown_header, parent, false);
final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
final String displayName = getAccountDisplayName(c);
displayNameView.setText(displayName);
} else {
emailAddressView.setVisibility(View.VISIBLE);
emailAddressView.setText(emailAddress);
}
view = mInflater.inflate(R.layout.account_selector_dropdown, parent, false);
final TextView displayNameView = (TextView) view.findViewById(R.id.display_name);
final TextView emailAddressView = (TextView) view.findViewById(R.id.email_address);
final TextView unreadCountView = (TextView) view.findViewById(R.id.unread_count);
unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext,
getAccountUnreadCount(position), false));
final String displayName = getAccountDisplayName(position);
final String emailAddress = getAccountEmailAddress(position);
displayNameView.setText(displayName);
// Show the email address only when it's different from the display name.
if (displayName.equals(emailAddress)) {
emailAddressView.setVisibility(View.GONE);
} else {
emailAddressView.setVisibility(View.VISIBLE);
emailAddressView.setText(emailAddress);
}
unreadCountView.setText(UiUtilities.getMessageCountForUi(mContext,
getAccountUnreadCount(position), false));
}
return view;
}
@ -118,9 +135,29 @@ public class AccountSelectorAdapter extends CursorAdapter {
return mInflater.inflate(R.layout.account_selector, parent, false);
}
/** @return Account id extracted from a Cursor. */
public static long getAccountId(Cursor c) {
return c.getLong(ID_COLUMN);
@Override
public int getViewTypeCount() {
return 2;
}
@Override
public int getItemViewType(int position) {
Cursor c = getCursor();
c.moveToPosition(position);
return c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_HEADER
? AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER
: ITEM_VIEW_TYPE_ACCOUNT;
}
@Override
public boolean isEnabled(int position) {
return (getItemViewType(position) != AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER);
}
public boolean isAccountItem(int position) {
Cursor c = getCursor();
c.moveToPosition(position);
return (c.getLong(c.getColumnIndex(ROW_TYPE)) == ROW_TYPE_ACCOUNT);
}
private String getAccountDisplayName(int position) {
@ -138,19 +175,24 @@ public class AccountSelectorAdapter extends CursorAdapter {
return c.moveToPosition(position) ? getAccountUnreadCount(c) : 0;
}
/** @return Account name extracted from a Cursor. */
public static String getAccountDisplayName(Cursor cursor) {
return cursor.getString(DISPLAY_NAME_COLUMN);
/** Returns the account ID extracted from the given cursor. */
static long getAccountId(Cursor c) {
return c.getLong(c.getColumnIndex(Account.ID));
}
/** @return Email address extracted from a Cursor. */
public static String getAccountEmailAddress(Cursor cursor) {
return cursor.getString(EMAIL_ADDRESS_COLUMN);
/** Returns the account name extracted from the given cursor. */
static String getAccountDisplayName(Cursor cursor) {
return cursor.getString(cursor.getColumnIndex(Account.DISPLAY_NAME));
}
/** @return Unread count extracted from a Cursor. */
public static int getAccountUnreadCount(Cursor cursor) {
return cursor.getInt(UNREAD_COUNT_COLUMN);
/** Returns the email address extracted from the given cursor. */
private static String getAccountEmailAddress(Cursor cursor) {
return cursor.getString(cursor.getColumnIndex(Account.EMAIL_ADDRESS));
}
/** Returns the unread count extracted from the given cursor. */
private static int getAccountUnreadCount(Cursor cursor) {
return cursor.getInt(cursor.getColumnIndex(UNREAD_COUNT));
}
/**
@ -159,7 +201,8 @@ public class AccountSelectorAdapter extends CursorAdapter {
* - # of unread messages in inbox
* - The "Combined view" row if there's more than one account.
*/
/* package */ static class AccountsLoader extends ThrottlingCursorLoader {
@VisibleForTesting
static class AccountsLoader extends ThrottlingCursorLoader {
private final Context mContext;
public AccountsLoader(Context context) {
@ -171,44 +214,51 @@ public class AccountSelectorAdapter extends CursorAdapter {
@Override
public Cursor loadInBackground() {
// Fetch account list
final Cursor accountsCursor = super.loadInBackground();
// Cursor that's actually returned.
// Use ClosingMatrixCursor so that accountsCursor gets closed too when it's closed.
final MatrixCursor resultCursor = new ClosingMatrixCursor(RESULT_PROJECTION,
accountsCursor);
accountsCursor.moveToPosition(-1);
final MatrixCursor resultCursor
= new ClosingMatrixCursor(ADAPTER_PROJECTION, accountsCursor);
addAccountsToCursor(resultCursor, accountsCursor);
// TODO Add mailbox recent list to the end of the return cursor
return Utility.CloseTraceCursorWrapper.get(resultCursor);
}
// Build the cursor...
/** Adds the account list [with extra meta data] to the given matrix cursor */
private void addAccountsToCursor(MatrixCursor matrixCursor, Cursor accountCursor) {
accountCursor.moveToPosition(-1);
// Add a header for the accounts
matrixCursor.newRow()
.add(ROW_TYPE_HEADER)
.add(0L)
.add(mContext.getString(R.string.mailbox_list_account_selector_account_header))
.add(null)
.add(0L);
int totalUnread = 0;
while (accountsCursor.moveToNext()) {
while (accountCursor.moveToNext()) {
// Add account, with its unread count.
final long accountId = accountsCursor.getLong(0);
final long accountId = accountCursor.getLong(0);
final int unread = Mailbox.getUnreadCountByAccountAndMailboxType(
mContext, accountId, Mailbox.TYPE_INBOX);
RowBuilder rb = resultCursor.newRow();
rb.add(accountId);
rb.add(getAccountDisplayName(accountsCursor));
rb.add(getAccountEmailAddress(accountsCursor));
rb.add(unread);
matrixCursor.newRow()
.add(ROW_TYPE_ACCOUNT)
.add(accountId)
.add(getAccountDisplayName(accountCursor))
.add(getAccountEmailAddress(accountCursor))
.add(unread);
totalUnread += unread;
}
// Add "combined view"
final int countAccounts = resultCursor.getCount();
// Add "combined view" if more than one account exists
final int countAccounts = matrixCursor.getCount();
if (countAccounts > 1) {
RowBuilder rb = resultCursor.newRow();
// Add ID, display name, # of accounts, total unread count.
rb.add(Account.ACCOUNT_ID_COMBINED_VIEW);
rb.add(mContext.getResources().getString(
R.string.mailbox_list_account_selector_combined_view));
rb.add(mContext.getResources().getQuantityString(R.plurals.number_of_accounts,
countAccounts, countAccounts));
rb.add(totalUnread);
matrixCursor.newRow()
.add(ROW_TYPE_ACCOUNT)
.add(Account.ACCOUNT_ID_COMBINED_VIEW)
.add(mContext.getResources().getString(
R.string.mailbox_list_account_selector_combined_view))
.add(mContext.getResources().getQuantityString(R.plurals.number_of_accounts,
countAccounts, countAccounts))
.add(totalUnread);
}
return Utility.CloseTraceCursorWrapper.get(resultCursor);
}
}
}

View File

@ -17,7 +17,6 @@
package com.android.email.activity;
import com.android.email.R;
import com.android.emailcommon.Logging;
import com.android.emailcommon.provider.EmailContent.Account;
import android.app.ActionBar;
@ -50,7 +49,9 @@ public class ActionBarController {
new ActionBarNavigationCallback();
private final AccountSelectorAdapter mAccountsSelectorAdapter;
private Cursor mAccountsSelectorCursor;
private Cursor mAccountCursor;
/** The current account ID; used to determine if the account has changed. */
private long mLastAccountIdForDirtyCheck = -1;
public final Callback mCallback;
@ -123,12 +124,7 @@ public class ActionBarController {
refresh();
}
/** Used only in {@link #refresh()} to determine if the account has changed. */
private long mLastAccountIdForDirtyCheck = -1;
/**
* Refresh the content.
*/
/** Refreshes the action bar display. */
public void refresh() {
mActionBar.setDisplayOptions(mCallback.shouldShowUp()
? ActionBar.DISPLAY_HOME_AS_UP : 0, ActionBar.DISPLAY_HOME_AS_UP);
@ -146,6 +142,7 @@ public class ActionBarController {
// Update the account list only when the account has changed.
if (mLastAccountIdForDirtyCheck != mCallback.getUIAccountId()) {
mLastAccountIdForDirtyCheck = mCallback.getUIAccountId();
// TODO Need to do this all the time as the recent list is shown here
updateAccountList();
}
}
@ -163,13 +160,13 @@ public class ActionBarController {
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAccountsSelectorCursor = data;
mAccountCursor = data;
updateAccountList();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAccountsSelectorCursor = null;
mAccountCursor = null;
updateAccountList();
}
});
@ -180,10 +177,10 @@ public class ActionBarController {
* on the action bar.
*/
private void updateAccountList() {
mAccountsSelectorAdapter.swapCursor(mAccountsSelectorCursor);
mAccountsSelectorAdapter.swapCursor(mAccountCursor);
final ActionBar ab = mActionBar;
if (mAccountsSelectorCursor == null) {
if (mAccountCursor == null) {
// Cursor not ready or closed.
mAccountsSelectorAdapter.swapCursor(null);
ab.setDisplayOptions(0, ActionBar.DISPLAY_SHOW_TITLE);
@ -191,7 +188,7 @@ public class ActionBarController {
return;
}
final int count = mAccountsSelectorCursor.getCount();
final int count = mAccountCursor.getCount();
if (count == 0) {
mCallback.onNoAccountsFound();
return;
@ -199,12 +196,12 @@ public class ActionBarController {
// If only one acount, don't show the dropdown.
if (count == 1) {
mAccountsSelectorCursor.moveToFirst();
mAccountCursor.moveToFirst();
// Show the account name as the title.
ab.setDisplayOptions(ActionBar.DISPLAY_SHOW_TITLE, ActionBar.DISPLAY_SHOW_TITLE);
ab.setNavigationMode(ActionBar.NAVIGATION_MODE_STANDARD);
ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(mAccountsSelectorCursor));
ab.setTitle(AccountSelectorAdapter.getAccountDisplayName(mAccountCursor));
return;
}
@ -212,10 +209,10 @@ public class ActionBarController {
int defaultSelection = 0;
if (mCallback.isAccountSelected()) {
final long accountId = mCallback.getUIAccountId();
mAccountsSelectorCursor.moveToPosition(-1);
mAccountCursor.moveToPosition(-1);
int i = 0;
while (mAccountsSelectorCursor.moveToNext()) {
if (accountId == AccountSelectorAdapter.getAccountId(mAccountsSelectorCursor)) {
while (mAccountCursor.moveToNext()) {
if (accountId == AccountSelectorAdapter.getAccountId(mAccountCursor)) {
defaultSelection = i;
break;
}
@ -234,9 +231,12 @@ public class ActionBarController {
private class ActionBarNavigationCallback implements ActionBar.OnNavigationListener {
@Override
public boolean onNavigationItemSelected(int itemPosition, long accountId) {
if (accountId != mCallback.getUIAccountId()) {
mCallback.onAccountSelected(accountId);
public boolean onNavigationItemSelected(int itemPosition, long itemId) {
if (mAccountsSelectorAdapter.isAccountItem(itemPosition)
&& itemId != mCallback.getUIAccountId()) {
mCallback.onAccountSelected(itemId);
} else {
// TODO handle mailbox item
}
return true;
}