blob: caf32defe590f9a795322ebde58d2890992ef4b5 [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 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.providers.UIProvider.AutoAdvance;
import com.android.mail.providers.UIProvider.ConversationColumns;
import com.android.mail.utils.LogUtils;
import java.util.ArrayList;
import java.util.Collections;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.MenuItem;
/**
* Controller for one-pane Mail activity. One Pane is used for phones, where screen real estate is
* limited.
*/
// Called OnePaneActivityController in Gmail.
public final class TwoPaneController extends AbstractActivityController {
private boolean mJumpToFirstConversation;
private TwoPaneLayout mLayout;
private final ActionCompleteListener mDeleteListener = new TwoPaneDestructiveActionListener(
R.id.delete);
private final ActionCompleteListener mArchiveListener = new TwoPaneDestructiveActionListener(
R.id.archive);
private final ActionCompleteListener mMuteListener = new TwoPaneDestructiveActionListener(
R.id.mute);
private final ActionCompleteListener mSpamListener = new TwoPaneDestructiveActionListener(
R.id.report_spam);
private final TwoPaneDestructiveActionListener mFolderChangeListener =
new TwoPaneDestructiveActionListener(R.id.change_folder);
/**
* @param activity
* @param viewMode
*/
public TwoPaneController(MailActivity activity, ViewMode viewMode) {
super(activity, viewMode);
}
/**
* Display the conversation list fragment.
* @param show
*/
private void initializeConversationListFragment(boolean show) {
if (show) {
if (mConvListContext != null && mConvListContext.isSearchResult()) {
mViewMode.enterSearchResultsListMode();
} else {
mViewMode.enterConversationListMode();
}
}
renderConversationList();
}
/**
* Render the conversation list in the correct pane.
*/
private void renderConversationList() {
if (mActivity == null) {
return;
}
FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
// Use cross fading animation.
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE);
Fragment conversationListFragment = ConversationListFragment
.newInstance(mConvListContext);
fragmentTransaction.replace(R.id.conversation_list_pane, conversationListFragment);
fragmentTransaction.commitAllowingStateLoss();
}
/**
* Render the folder list in the correct pane.
*/
private void renderFolderList() {
if (mActivity == null) {
return;
}
createFolderListFragment(null, mAccount.folderListUri);
}
private void createFolderListFragment(Folder parent, Uri uri) {
FolderListFragment folderListFragment = FolderListFragment.newInstance(parent, uri);
FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.replace(R.id.content_pane, folderListFragment);
fragmentTransaction.commitAllowingStateLoss();
// Since we are showing the folder list, we are at the start of the view
// stack.
resetActionBarIcon();
attachFolderList(folderListFragment);
if (getCurrentListContext() != null) {
folderListFragment.selectFolder(getCurrentListContext().folder);
}
}
@Override
protected boolean isConversationListVisible() {
// TODO(viki): Auto-generated method stub
return false;
}
@Override
public void showConversationList(ConversationListContext listContext) {
super.showConversationList(listContext);
initializeConversationListFragment(true);
}
@Override
public void showFolderList() {
// On two-pane layouts, showing the folder list takes you to the top level of the
// application, which is the same as pressing the Up button
onUpPressed();
}
@Override
public boolean onCreate(Bundle savedState) {
mActivity.setContentView(R.layout.two_pane_activity);
mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity);
if (mLayout == null) {
LogUtils.d(LOG_TAG, "mLayout is null!");
}
mLayout.initializeLayout(mActivity.getApplicationContext());
// The tablet layout needs to refer to mode changes.
mViewMode.addListener(mLayout);
// The activity controller needs to listen to layout changes.
mLayout.setListener(this);
final boolean isParentInitialized = super.onCreate(savedState);
return isParentInitialized;
}
@Override
public void onAccountChanged(Account account) {
super.onAccountChanged(account);
renderFolderList();
}
@Override
public void onFolderSelected(Folder folder, boolean childView) {
if (!childView && folder.hasChildren) {
// 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.
createFolderListFragment(folder, folder.childFoldersListUri);
// Show the up affordance when digging into child folders.
mActionBarView.setBackButton();
return;
}
if (mFolderListFragment != null) {
mFolderListFragment.selectFolder(folder);
}
super.onFolderChanged(folder);
}
@Override
public void onViewModeChanged(int newMode) {
super.onViewModeChanged(newMode);
if (newMode != ViewMode.CONVERSATION) {
// Clear this flag if the user jumps out of conversation mode
// before a load completes.
mJumpToFirstConversation = false;
}
resetActionBarIcon();
}
@Override
public void resetActionBarIcon() {
if (mViewMode.getMode() == ViewMode.CONVERSATION_LIST) {
mActionBarView.removeBackButton();
} else {
mActionBarView.setBackButton();
}
}
@Override
public void showConversation(Conversation conversation) {
if (mActivity == null) {
return;
}
super.showConversation(conversation);
int mode = mViewMode.getMode();
if (mode == ViewMode.SEARCH_RESULTS_LIST || mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
mViewMode.enterSearchResultsConversationMode();
unhideConversationList();
} else {
mViewMode.enterConversationMode();
}
Fragment convFragment = ConversationViewFragment.newInstance(mAccount, conversation,
mFolder);
FragmentTransaction fragmentTransaction = mActivity.getFragmentManager().beginTransaction();
fragmentTransaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN);
fragmentTransaction.replace(R.id.conversation_pane, convFragment);
fragmentTransaction.commitAllowingStateLoss();
}
/**
* Show the conversation list if it can be shown in the current orientation.
* @return true if the conversation list was shown
*/
private boolean unhideConversationList() {
// Find if the conversation list can be shown
int mode = mViewMode.getMode();
final boolean isConversationListShowable = (mode == ViewMode.CONVERSATION
&& mLayout.isConversationListCollapsible()
|| (mode == ViewMode.SEARCH_RESULTS_CONVERSATION));
if (isConversationListShowable) {
return mLayout.uncollapseList();
}
return false;
}
/**
* Up works as follows:
* 1) If the user is in a conversation and:
* a) the conversation list is hidden (portrait mode), shows the conv list and
* stays in conversation view mode.
* b) the conversation list is shown, goes back to conversation list mode.
* 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 conversation list mode, there is no up.
*/
@Override
public boolean onUpPressed() {
int mode = mViewMode.getMode();
if (mode == ViewMode.CONVERSATION) {
if (!mLayout.isConversationListVisible()) {
unhideConversationList();
} else {
mActivity.onBackPressed();
}
} else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
if (!mLayout.isConversationListVisible()) {
unhideConversationList();
} else {
mActivity.finish();
}
} else if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
} else if (mode == ViewMode.CONVERSATION_LIST) {
// This case can only happen if the user is looking at child folders.
createFolderListFragment(null, mAccount.folderListUri);
loadAccountInbox();
}
return true;
}
@Override
public boolean onBackPressed() {
popView(false);
return true;
}
/**
* Pops the "view stack" to the last screen the user was viewing.
*
* @param preventClose Whether to prevent closing the app if the stack is empty.
*/
protected void popView(boolean preventClose) {
// If the user is in search query entry mode, or the user is viewing search results, exit
// the mode.
int mode = mViewMode.getMode();
if (mode == ViewMode.SEARCH_RESULTS_LIST) {
mActivity.finish();
} else if (mViewMode.getMode() == ViewMode.CONVERSATION) {
// Go to conversation list.
mViewMode.enterConversationListMode();
} else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
mViewMode.enterSearchResultsListMode();
} else {
// There is nothing else to pop off the stack.
if (!preventClose) {
mActivity.finish();
}
}
}
@Override
public boolean shouldShowFirstConversation() {
return mConvListContext != null && mConvListContext.isSearchResult();
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
boolean handled = true;
final int id = item.getItemId();
switch (id) {
case R.id.y_button: {
final Settings settings = mActivity.getSettings();
final boolean showDialog = (settings != null && settings.confirmArchive);
confirmAndDelete(showDialog, R.plurals.confirm_archive_conversation,
mArchiveListener);
break;
}
case R.id.delete: {
final Settings settings = mActivity.getSettings();
final boolean showDialog = (settings != null && settings.confirmDelete);
confirmAndDelete(showDialog, R.plurals.confirm_delete_conversation,
mDeleteListener);
break;
}
case R.id.change_folders:
new FoldersSelectionDialog(mActivity.getActivityContext(), mAccount, this,
Collections.singletonList(mCurrentConversation)).show();
break;
case R.id.inside_conversation_unread:
updateCurrentConversation(ConversationColumns.READ, false);
break;
case R.id.mark_important:
updateCurrentConversation(ConversationColumns.PRIORITY,
UIProvider.ConversationPriority.HIGH);
break;
case R.id.mark_not_important:
updateCurrentConversation(ConversationColumns.PRIORITY,
UIProvider.ConversationPriority.LOW);
break;
case R.id.mute:
mConversationListFragment.requestDelete(mMuteListener);
break;
case R.id.report_spam:
mConversationListFragment.requestDelete(mSpamListener);
break;
default:
handled = false;
break;
}
return handled || super.onOptionsItemSelected(item);
}
/**
* An object that performs an action on the conversation database. This is an
* ActionCompleteListener since this is called <b>after</a> the conversation list has animated
* the conversation away. Once the animation is completed, the {@link #onActionComplete()}
* method is called which performs the correct data operation.
*/
private class TwoPaneDestructiveActionListener extends DestructiveActionListener {
public TwoPaneDestructiveActionListener(int action) {
super(action);
}
@Override
public void onActionComplete() {
final ArrayList<Conversation> single = new ArrayList<Conversation>();
single.add(mCurrentConversation);
int next = -1;
final int pref = getAutoAdvanceSetting(mActivity);
final Cursor c = mConversationListCursor;
int updatedPosition = -1;
final int position = mCurrentConversation.position;
if (c != null) {
switch (pref) {
case AutoAdvance.NEWER:
if (position - 1 >= 0) {
// This conversation was deleted, so to get to the previous
// conversation, show what is now in its position - 1.
next = position - 1;
// The position is correct, since no items before this have
// been deleted.
updatedPosition = position - 1;
}
break;
case AutoAdvance.OLDER:
if (position + 1 < c.getCount()) {
// This conversation was deleted, so to get to the next
// conversation, show what is now in position + 1.
next = position + 1;
// Since this conversation was deleted, update the conversation
// we are showing to have the position this conversation was in.
updatedPosition = position;
}
break;
}
}
TwoPaneController.this.onActionComplete();
mConversationListFragment.onUndoAvailable(new UndoOperation(1, mAction));
if (next != -1) {
mConversationListFragment.viewConversation(next);
mCurrentConversation.position = updatedPosition;
} else {
onBackPressed();
}
performConversationAction(single);
mConversationListFragment.requestListRefresh();
}
}
@Override
protected void requestDelete(final ActionCompleteListener listener) {
mConversationListFragment.requestDelete(listener);
}
@Override
protected DestructiveActionListener getFolderDestructiveActionListener() {
return mFolderChangeListener;
}
}