blob: dc7a68475979443adf18906949f91fe634c5bbbf [file] [log] [blame]
/*******************************************************************************
* Copyright (C) 2012 Google Inc.
* Licensed to 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.
*******************************************************************************/
package com.android.mail.ui;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.net.Uri;
import android.os.Bundle;
import com.android.mail.ConversationListContext;
import com.android.mail.R;
import com.android.mail.providers.Account;
import com.android.mail.providers.Conversation;
import com.android.mail.providers.Folder;
import com.android.mail.providers.Settings;
import com.android.mail.providers.UIProvider;
import com.android.mail.utils.LogUtils;
/**
* Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
* limited. This controller also does the layout, since the layout is simpler in the one pane case.
*/
// Called OnePaneActivityController in Gmail.
public final class OnePaneController extends AbstractActivityController {
// Used for saving transaction IDs in bundles
private static final String FOLDER_LIST_TRANSACTION_KEY = "folder-list-transaction";
private static final String INBOX_CONVERSATION_LIST_TRANSACTION_KEY =
"inbox_conversation-list-transaction";
private static final String CONVERSATION_LIST_TRANSACTION_KEY = "conversation-list-transaction";
private static final String CONVERSATION_TRANSACTION_KEY = "conversation-transaction";
private static final int INVALID_ID = -1;
private boolean mConversationListVisible = false;
private int mLastInboxConversationListTransactionId = INVALID_ID;
private int mLastConversationListTransactionId = INVALID_ID;
private int mLastConversationTransactionId = INVALID_ID;
private int mLastFolderListTransactionId = INVALID_ID;
private Folder mInbox;
/** Whether a conversation list for this account has ever been shown.*/
private boolean mConversationListNeverShown = true;
/**
* @param activity
* @param viewMode
*/
public OnePaneController(MailActivity activity, ViewMode viewMode) {
super(activity, viewMode);
}
@Override
public void onRestoreInstanceState(Bundle inState) {
super.onRestoreInstanceState(inState);
// TODO(mindyp) handle saved state.
if (inState != null) {
mLastFolderListTransactionId = inState.getInt(FOLDER_LIST_TRANSACTION_KEY, INVALID_ID);
mLastInboxConversationListTransactionId =
inState.getInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY, INVALID_ID);
mLastConversationListTransactionId = inState.getInt(CONVERSATION_LIST_TRANSACTION_KEY,
INVALID_ID);
mLastConversationTransactionId = inState.getInt(CONVERSATION_TRANSACTION_KEY,
INVALID_ID);
}
}
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// TODO(mindyp) handle saved state.
outState.putInt(FOLDER_LIST_TRANSACTION_KEY, mLastFolderListTransactionId);
outState.putInt(INBOX_CONVERSATION_LIST_TRANSACTION_KEY,
mLastInboxConversationListTransactionId);
outState.putInt(CONVERSATION_LIST_TRANSACTION_KEY, mLastConversationListTransactionId);
outState.putInt(CONVERSATION_TRANSACTION_KEY, mLastConversationTransactionId);
}
@Override
public void resetActionBarIcon() {
final int mode = mViewMode.getMode();
if (!inInbox(mAccount, mConvListContext)
|| mode == ViewMode.SEARCH_RESULTS_LIST
|| mode == ViewMode.SEARCH_RESULTS_CONVERSATION
|| mode == ViewMode.CONVERSATION
|| mode == ViewMode.FOLDER_LIST) {
mActionBarView.setBackButton();
} else {
mActionBarView.removeBackButton();
}
}
/**
* Returns true if the user is currently in the conversation list view, viewing the default
* inbox.
* @return
*/
private static boolean inInbox(final Account account, final ConversationListContext context) {
// If we don't have valid state, then we are not in the inbox.
if (account == null || context == null || context.folder == null
|| account.settings == null) {
return false;
}
final Uri inboxUri = Settings.getDefaultInboxUri(account.settings);
return !context.isSearchResult() && context.folder.uri.equals(inboxUri);
}
@Override
public void onAccountChanged(Account account) {
super.onAccountChanged(account);
mConversationListNeverShown = true;
}
@Override
public boolean onCreate(Bundle savedInstanceState) {
mActivity.setContentView(R.layout.one_pane_activity);
// The parent class sets the correct viewmode and starts the application off.
return super.onCreate(savedInstanceState);
}
@Override
protected boolean isConversationListVisible() {
return mConversationListVisible;
}
@Override
public void onViewModeChanged(int newMode) {
super.onViewModeChanged(newMode);
// When entering conversation list mode, hide and clean up any currently visible
// conversation.
// TODO: improve this transition
if (newMode == ViewMode.CONVERSATION_LIST) {
mPagerController.hide();
}
}
@Override
public void showConversationList(ConversationListContext listContext) {
super.showConversationList(listContext);
enableCabMode();
// TODO(viki): Check if the account has been changed since the previous
// time.
if (listContext != null && listContext.isSearchResult()) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
// TODO(viki): This account transition looks strange in two pane mode.
// Revisit as the app is coming together and improve the look and feel.
final int transition = mConversationListNeverShown
? FragmentTransaction.TRANSIT_FRAGMENT_FADE
: FragmentTransaction.TRANSIT_FRAGMENT_OPEN;
Fragment conversationListFragment = ConversationListFragment.newInstance(listContext);
if (!inInbox(mAccount, mConvListContext)) {
// Maintain fragment transaction history so we can get back to the
// fragment used to launch this list.
mLastConversationListTransactionId = replaceFragment(conversationListFragment,
transition, TAG_CONVERSATION_LIST);
} else {
// If going to the inbox, clear the folder list transaction history.
mInbox = listContext.folder;
mLastInboxConversationListTransactionId = replaceFragment(conversationListFragment,
transition, TAG_CONVERSATION_LIST);
mLastFolderListTransactionId = INVALID_ID;
// If we ever to to the inbox, we want to unset the transation id for any other
// non-inbox folder.
mLastConversationListTransactionId = INVALID_ID;
}
mConversationListVisible = true;
onConversationVisibilityChanged(false);
onConversationListVisibilityChanged(true);
mConversationListNeverShown = false;
}
@Override
public void showConversation(Conversation conversation) {
super.showConversation(conversation);
if (conversation == null) {
// This is a request to remove the conversation view, and pop back the view stack.
// If we are in conversation list view already, this should be a safe thing to do, so
// we don't check viewmode.
transitionBackToConversationListMode();
return;
}
disableCabMode();
if (mConvListContext != null && mConvListContext.isSearchResult()) {
mViewMode.enterSearchResultsConversationMode();
} else {
mViewMode.enterConversationMode();
}
// Switching to conversation view is an incongruous transition: we are not replacing a
// fragment with another fragment as usual. Instead, reveal the heretofore inert
// conversation ViewPager and just remove the previously visible fragment
// (e.g. conversation list, or possibly label list?).
final FragmentManager fm = mActivity.getFragmentManager();
final Fragment f = fm.findFragmentById(R.id.content_pane);
if (f != null) {
final FragmentTransaction ft = fm.beginTransaction();
ft.addToBackStack(null);
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
ft.remove(f);
ft.commitAllowingStateLoss();
}
// TODO: improve this transition
mPagerController.show(mAccount, getFolder(), conversation);
onConversationVisibilityChanged(true);
resetActionBarIcon();
mConversationListVisible = false;
onConversationListVisibilityChanged(false);
}
@Override
public void showWaitForInitialization() {
super.showWaitForInitialization();
replaceFragment(WaitFragment.newInstance(mAccount),
FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_WAIT);
}
@Override
public void hideWaitForInitialization() {
transitionToInbox();
}
@Override
public void showFolderList() {
if (mAccount == null) {
LogUtils.e(LOG_TAG, "Null account in showFolderList");
return;
}
// Null out the currently selected folder; we have nothing selected the
// first time the user enters the folder list
setFolder(null);
mViewMode.enterFolderListMode();
enableCabMode();
mLastFolderListTransactionId = replaceFragment(
FolderListFragment.newInstance(null, mAccount.folderListUri),
FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST);
mConversationListVisible = false;
onConversationVisibilityChanged(false);
onConversationListVisibilityChanged(false);
}
/**
* Replace the content_pane with the fragment specified here. The tag is specified so that
* the {@link ActivityController} can look up the fragments through the
* {@link android.app.FragmentManager}.
* @param fragment
* @param transition
* @param tag
* @return transaction ID returned when the transition is committed.
*/
private int replaceFragment(Fragment fragment, int transition, String tag) {
FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
fragmentTransaction.addToBackStack(null);
fragmentTransaction.setTransition(transition);
fragmentTransaction.replace(R.id.content_pane, fragment, tag);
final int transactionId = fragmentTransaction.commitAllowingStateLoss();
resetActionBarIcon();
return transactionId;
}
/**
* Back works as follows:
* 1) If the user is in the folder list view, go back
* to the account default inbox.
* 2) If the user is in a conversation list
* that is not the inbox AND:
* a) they got there by going through the folder
* list view, go back to the folder list view.
* b) they got there by using some other means (account dropdown), go back to the inbox.
* 3) If the user is in a conversation, go back to the conversation list they were last in.
* 4) If the user is in the conversation list for the default account inbox,
* back exits the app.
*/
@Override
public boolean onBackPressed() {
final int mode = mViewMode.getMode();
if (mode == ViewMode.FOLDER_LIST) {
if (getFolderListFragment().showingHierarchy()) {
// If we are showing the folder list and the user is exploring
// the children of a single parent folder,
// back should display the parent folder's parent and siblings.
if (getFolder() != null && getFolder().parent != null) {
onFolderSelected(getFolder().parent, true);
} else {
// If there was no parent, this must have been a top level
// folder, so just show the top level folder list.
showFolderList();
}
} else {
// We are at the topmost list of folders; just go back to
// whatever conv list we were viewing before.
mLastFolderListTransactionId = INVALID_ID;
transitionToInbox();
}
} else if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
} else if (mode == ViewMode.CONVERSATION_LIST && !inInbox(mAccount, mConvListContext)) {
if (mLastFolderListTransactionId != INVALID_ID) {
// If the user got here by navigating via the folder list, back
// should bring them back to the folder list.
mViewMode.enterFolderListMode();
if (getFolder() != null && getFolder().parent != null) {
// If there was a parent folder, show the parent and
// siblings of the current folder for which we are viewing
// the conversation list.
setFolder(getFolder().parent);
} else {
// Otherwise, clear the selected folder and go back to whatever the last
// folder list displayed was.
setFolder(null);
}
mActivity.getFragmentManager().popBackStack(mLastFolderListTransactionId, 0);
} else {
mLastFolderListTransactionId = INVALID_ID;
transitionToInbox();
}
} else if (mode == ViewMode.CONVERSATION || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
transitionBackToConversationListMode();
} else {
mActivity.finish();
}
mUndoBarView.hide(false);
return true;
}
private void transitionToInbox() {
mViewMode.enterConversationListMode();
if (mInbox == null) {
loadAccountInbox();
} else {
ConversationListContext listContext = ConversationListContext.forFolder(mContext,
mAccount, mInbox);
// Set the correct context for what the conversation view will be
// now.
onFolderChanged(mInbox);
showConversationList(listContext);
}
}
@Override
public void onFolderSelected(Folder folder, boolean childView) {
if (folder.hasChildren && !getFolderListFragment().showingHierarchy()) {
setFolder(folder);
// Replace this fragment with a new FolderListFragment
// showing this folder's children if we are not already
// looking at the child view for this folder.
mLastFolderListTransactionId = replaceFragment(
FolderListFragment.newInstance(folder, folder.childFoldersListUri),
FragmentTransaction.TRANSIT_FRAGMENT_OPEN, TAG_FOLDER_LIST);
return;
} else {
// We are looking at the child folders of this folder, so just
// open the conv list for this folder.
// We set the folder to null to clear the selected folder and
// make sure that everything gets updated in case we were previously
// viewing the child folders or conversation list for the selected folder.
setFolder(null);
super.onFolderChanged(folder);
}
}
private boolean isTransactionIdValid(int id) {
return id >= 0;
}
/**
* Up works as follows:
* 1) If the user is in a conversation list that is not the default account inbox,
* a conversation, or the folder list, up follows the rules of back.
* 2) If the user is in search results, up exits search
* mode and returns the user to whatever view they were in when they began search.
* 3) If the user is in the inbox, there is no up.
*/
@Override
public boolean onUpPressed() {
final int mode = mViewMode.getMode();
if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
} else if ((!inInbox(mAccount, mConvListContext) && mode == ViewMode.CONVERSATION_LIST)
|| mode == ViewMode.CONVERSATION
|| mode == ViewMode.FOLDER_LIST
|| mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
// Same as go back.
mActivity.onBackPressed();
}
return true;
}
private void transitionBackToConversationListMode() {
final int mode = mViewMode.getMode();
enableCabMode();
if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
if (isTransactionIdValid(mLastConversationListTransactionId)) {
mActivity.getFragmentManager().popBackStack(mLastConversationListTransactionId, 0);
resetActionBarIcon();
} else if (isTransactionIdValid(mLastInboxConversationListTransactionId)) {
mActivity.getFragmentManager().popBackStack(mLastInboxConversationListTransactionId, 0);
resetActionBarIcon();
onFolderChanged(mInbox);
} else {
// TODO: revisit if this block is necessary
final ConversationListContext listContext = ConversationListContext.forFolder(mContext,
mAccount, mInbox);
// Set the correct context for what the conversation view will be now.
onFolderChanged(mInbox);
showConversationList(listContext);
}
resetActionBarIcon();
mConversationListVisible = true;
onConversationVisibilityChanged(false);
onConversationListVisibilityChanged(true);
}
@Override
public boolean shouldShowFirstConversation() {
return false;
}
@Override
public void onUndoAvailable(UndoOperation op) {
if (op != null && mAccount.supportsCapability(UIProvider.AccountCapabilities.UNDO)) {
final int mode = mViewMode.getMode();
switch (mode) {
case ViewMode.CONVERSATION:
mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount,
null, null);
break;
case ViewMode.CONVERSATION_LIST:
final ConversationListFragment convList = getConversationListFragment();
if (convList != null) {
mUndoBarView.show(true, mActivity.getActivityContext(), op, mAccount,
convList.getAnimatedAdapter(), mConversationListCursor);
}
break;
}
}
}
}