summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorAndy Huang <ath@google.com>2014-07-09 16:58:18 -0700
committerAndy Huang <ath@google.com>2014-08-01 23:32:51 +0000
commitf58e4c3c942033fce12b5f75f9e4d9e708c9ea6a (patch)
tree178af5cda782c668fa93c4cf857787429f2195f9 /src
parent3df312385775e959ba7068d315035566cf605028 (diff)
downloadandroid_packages_apps_UnifiedEmail-f58e4c3c942033fce12b5f75f9e4d9e708c9ea6a.tar.gz
android_packages_apps_UnifiedEmail-f58e4c3c942033fce12b5f75f9e4d9e708c9ea6a.tar.bz2
android_packages_apps_UnifiedEmail-f58e4c3c942033fce12b5f75f9e4d9e708c9ea6a.zip
mini-drawer. new tablet UI.
New MiniDrawerView class for a minimized drawer UI with shortcuts to the main drawer logic to switch accounts and folders. The "drawer" is now always visible except in portrait conversation view. Can't easily use an actual DrawerLayout because: -drawers usually occlude other views, they don't push -we have that omnipresent 'mini' version -we want custom control over dragging to trigger a fancy animation (not just edge swipe!) (I'll revisit this later.) Even ActionBarDrawerToggle alone can't be used w/o DrawerLayout, so for now, all of this drawer logic (dragging not yet implemented) is from scratch. TwoPaneLayout no longer "shifts" its panes to transition from TL->CV in landscape; the panes are now fixed in position. Not yet implemented is a 'conversation-visible-but-not-marked-read' state necessary to avoid the initial gray expanse there right now. Bug: 16147175 Change-Id: I021aaff15afebb76db6722265e2a592213674405
Diffstat (limited to 'src')
-rw-r--r--src/com/android/mail/bitmap/AccountAvatarDrawable.java15
-rw-r--r--src/com/android/mail/browse/ConversationItemView.java30
-rw-r--r--src/com/android/mail/browse/ConversationPagerController.java2
-rw-r--r--src/com/android/mail/ui/AbstractActivityController.java52
-rw-r--r--src/com/android/mail/ui/ConversationListCopy.java90
-rw-r--r--src/com/android/mail/ui/ConversationViewFrame.java58
-rw-r--r--src/com/android/mail/ui/DrawerController.java1
-rw-r--r--src/com/android/mail/ui/FolderListFragment.java150
-rw-r--r--src/com/android/mail/ui/MiniDrawerView.java202
-rw-r--r--src/com/android/mail/ui/OnePaneController.java4
-rw-r--r--src/com/android/mail/ui/TwoPaneController.java147
-rw-r--r--src/com/android/mail/ui/TwoPaneLayout.java324
-rw-r--r--src/com/android/mail/utils/Utils.java3
13 files changed, 610 insertions, 468 deletions
diff --git a/src/com/android/mail/bitmap/AccountAvatarDrawable.java b/src/com/android/mail/bitmap/AccountAvatarDrawable.java
index 20beae0cf..21f14c1b6 100644
--- a/src/com/android/mail/bitmap/AccountAvatarDrawable.java
+++ b/src/com/android/mail/bitmap/AccountAvatarDrawable.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright (C) 2014 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.bitmap;
import android.content.res.Resources;
diff --git a/src/com/android/mail/browse/ConversationItemView.java b/src/com/android/mail/browse/ConversationItemView.java
index 0d4ae2f63..9f0405de4 100644
--- a/src/com/android/mail/browse/ConversationItemView.java
+++ b/src/com/android/mail/browse/ConversationItemView.java
@@ -130,7 +130,7 @@ public class ConversationItemView extends View
private static Bitmap STATE_FORWARDED;
private static Bitmap STATE_REPLIED_AND_FORWARDED;
private static Bitmap STATE_CALENDAR_INVITE;
- private static Bitmap VISIBLE_CONVERSATION_CARET;
+ private static Drawable VISIBLE_CONVERSATION_HIGHLIGHT;
private static Drawable RIGHT_EDGE_TABLET;
private static String sSendersSplitToken;
@@ -173,8 +173,6 @@ public class ConversationItemView extends View
/** Whether we are on a tablet device or not */
private final boolean mTabletDevice;
- /** Whether we are on an expansive tablet */
- private final boolean mIsExpansiveTablet;
/** When in conversation mode, true if the list is hidden */
private final boolean mListCollapsible;
@@ -384,8 +382,6 @@ public class ConversationItemView extends View
mContext = context.getApplicationContext();
final Resources res = mContext.getResources();
mTabletDevice = Utils.useTabletUI(res);
- mIsExpansiveTablet =
- mTabletDevice ? res.getBoolean(R.bool.use_expansive_tablet_ui) : false;
mListCollapsible = res.getBoolean(R.bool.list_collapsible);
mAccount = account;
@@ -452,7 +448,8 @@ public class ConversationItemView extends View
BitmapFactory.decodeResource(res, R.drawable.ic_badge_reply_forward_holo_light);
STATE_CALENDAR_INVITE =
BitmapFactory.decodeResource(res, R.drawable.ic_badge_invite_holo_light);
- VISIBLE_CONVERSATION_CARET = BitmapFactory.decodeResource(res, R.drawable.caret_grey);
+ VISIBLE_CONVERSATION_HIGHLIGHT = res.getDrawable(
+ R.drawable.visible_conversation_highlight);
RIGHT_EDGE_TABLET = res.getDrawable(R.drawable.list_edge_tablet);
// Initialize colors.
@@ -1298,20 +1295,11 @@ public class ConversationItemView extends View
RIGHT_EDGE_TABLET.draw(canvas);
if (isActivated()) {
- // draw caret on the end, centered vertically
- final int x = (isRtl) ? 0 : getWidth() - VISIBLE_CONVERSATION_CARET.getWidth();
- final int y = (getHeight() - VISIBLE_CONVERSATION_CARET.getHeight()) / 2;
- if (isRtl) {
- // draw the bitmap mirrored in RTL mode
- canvas.save();
- canvas.scale(-1, 1,
- x + VISIBLE_CONVERSATION_CARET.getWidth()/2,
- y + VISIBLE_CONVERSATION_CARET.getHeight()/2);
- canvas.drawBitmap(VISIBLE_CONVERSATION_CARET, x, y, null);
- canvas.restore();
- } else {
- canvas.drawBitmap(VISIBLE_CONVERSATION_CARET, x, y, null);
- }
+ final int w = VISIBLE_CONVERSATION_HIGHLIGHT.getIntrinsicWidth();
+ VISIBLE_CONVERSATION_HIGHLIGHT.setBounds(
+ (isRtl) ? getWidth() - w : 0, 0,
+ (isRtl) ? getWidth() : w, getHeight());
+ VISIBLE_CONVERSATION_HIGHLIGHT.draw(canvas);
}
}
Utils.traceEndSection();
@@ -1384,7 +1372,7 @@ public class ConversationItemView extends View
@Override
public boolean toggleSelectedStateOrBeginDrag() {
ViewMode mode = mActivity.getViewMode();
- if (mIsExpansiveTablet && mode.isListMode()) {
+ if (mTabletDevice && mode.isListMode()) {
return beginDragMode();
} else {
return toggleSelectedState("long_press");
diff --git a/src/com/android/mail/browse/ConversationPagerController.java b/src/com/android/mail/browse/ConversationPagerController.java
index cbfe195e3..ae576bda8 100644
--- a/src/com/android/mail/browse/ConversationPagerController.java
+++ b/src/com/android/mail/browse/ConversationPagerController.java
@@ -87,7 +87,7 @@ public class ConversationPagerController {
public ConversationPagerController(RestrictedActivity activity,
ActivityController controller) {
mFragmentManager = activity.getFragmentManager();
- mPager = (ViewPager) activity.findViewById(R.id.conversation_pane);
+ mPager = (ViewPager) activity.findViewById(R.id.conversation_pager);
mActivityController = controller;
setupPageMargin(activity.getActivityContext());
}
diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java
index 6f47bcee6..53c9c4bc7 100644
--- a/src/com/android/mail/ui/AbstractActivityController.java
+++ b/src/com/android/mail/ui/AbstractActivityController.java
@@ -497,6 +497,7 @@ public abstract class AbstractActivityController implements ActivityController,
protected DrawerLayout mDrawerContainer;
protected View mDrawerPullout;
protected ActionBarDrawerToggle mDrawerToggle;
+
protected ListView mListViewForAnimating;
protected boolean mHasNewAccountOrFolder;
private boolean mConversationListLoadFinishedIgnored;
@@ -875,15 +876,17 @@ public abstract class AbstractActivityController implements ActivityController,
@Override
public void onFolderChanged(Folder folder, final boolean force) {
- /** If the folder doesn't exist, or its parent URI is empty,
- * this is not a child folder */
- final boolean isTopLevel = Folder.isRoot(folder);
- final int mode = mViewMode.getMode();
- mDrawerToggle.setDrawerIndicatorEnabled(
- getShouldShowDrawerIndicator(mode, isTopLevel));
- mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
+ if (isDrawerEnabled()) {
+ /** If the folder doesn't exist, or its parent URI is empty,
+ * this is not a child folder */
+ final boolean isTopLevel = Folder.isRoot(folder);
+ final int mode = mViewMode.getMode();
+ mDrawerToggle.setDrawerIndicatorEnabled(
+ getShouldShowDrawerIndicator(mode, isTopLevel));
+ mDrawerContainer.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED);
- mDrawerContainer.closeDrawers();
+ mDrawerContainer.closeDrawers();
+ }
if (mFolder == null || !mFolder.equals(folder)) {
// We are actually changing the folder, so exit cab mode
@@ -1277,13 +1280,20 @@ public abstract class AbstractActivityController implements ActivityController,
mFloatingComposeButton = mActivity.findViewById(R.id.compose_button);
mFloatingComposeButton.setOnClickListener(this);
- mDrawerToggle = new ActionBarDrawerToggle(mActivity, mDrawerContainer, false,
- R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
- mDrawerContainer.setDrawerListener(mDrawerListener);
- mDrawerContainer.setDrawerShadow(
- mContext.getResources().getDrawable(R.drawable.drawer_shadow), Gravity.START);
+ if (isDrawerEnabled()) {
+ mDrawerToggle = new ActionBarDrawerToggle(mActivity, mDrawerContainer, false,
+ R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close);
+ mDrawerContainer.setDrawerListener(mDrawerListener);
+ mDrawerContainer.setDrawerShadow(
+ mContext.getResources().getDrawable(R.drawable.drawer_shadow), Gravity.START);
- mDrawerToggle.setDrawerIndicatorEnabled(isDrawerEnabled());
+ mDrawerToggle.setDrawerIndicatorEnabled(isDrawerEnabled());
+ } else {
+ final ActionBar ab = mActivity.getActionBar();
+ ab.setHomeAsUpIndicator(R.drawable.ic_drawer);
+ ab.setHomeActionContentDescription(R.string.drawer_open);
+ ab.setDisplayHomeAsUpEnabled(true);
+ }
// All the individual UI components listen for ViewMode changes. This
// simplifies the amount of logic in the AbstractActivityController, but increases the
@@ -1326,6 +1336,9 @@ public abstract class AbstractActivityController implements ActivityController,
@Override
public void onPostCreate(Bundle savedState) {
+ if (!isDrawerEnabled()) {
+ return;
+ }
// Sync the toggle state after onRestoreInstanceState has occurred.
mDrawerToggle.syncState();
@@ -1334,7 +1347,9 @@ public abstract class AbstractActivityController implements ActivityController,
@Override
public void onConfigurationChanged(Configuration newConfig) {
- mDrawerToggle.onConfigurationChanged(newConfig);
+ if (isDrawerEnabled()) {
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
}
/**
@@ -1462,7 +1477,7 @@ public abstract class AbstractActivityController implements ActivityController,
* The action bar home/up action should open or close the drawer.
* mDrawerToggle will take care of this.
*/
- if (mDrawerToggle.onOptionsItemSelected(item)) {
+ if (isDrawerEnabled() && mDrawerToggle.onOptionsItemSelected(item)) {
Analytics.getInstance().sendEvent(Analytics.EVENT_CATEGORY_MENU_ITEM, "drawer_toggle",
null, 0);
return true;
@@ -4305,6 +4320,11 @@ public abstract class AbstractActivityController implements ActivityController,
}
@Override
+ public void toggleDrawerState() {
+ AbstractActivityController.this.toggleDrawerState();
+ }
+
+ @Override
public void onDrawerOpened(View drawerView) {
mDrawerToggle.onDrawerOpened(drawerView);
diff --git a/src/com/android/mail/ui/ConversationListCopy.java b/src/com/android/mail/ui/ConversationListCopy.java
deleted file mode 100644
index 1100d24da..000000000
--- a/src/com/android/mail/ui/ConversationListCopy.java
+++ /dev/null
@@ -1,90 +0,0 @@
-/*******************************************************************************
- * 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.content.Context;
-import android.graphics.Bitmap;
-import android.graphics.Bitmap.Config;
-import android.graphics.Canvas;
-import android.util.AttributeSet;
-import android.view.View;
-
-import com.android.mail.utils.LogTag;
-import com.android.mail.utils.LogUtils;
-
-/**
- * A normally inert view that draws a bitmap copy of the existing conversation list view when
- * transitioning.
- *
- */
-public class ConversationListCopy extends View {
-
- private Bitmap mBitmap;
-
- private static final String LOG_TAG = LogTag.getLogTag();
-
- public ConversationListCopy(Context c) {
- this(c, null);
- }
-
- public ConversationListCopy(Context c, AttributeSet attrs) {
- super(c, attrs);
- }
-
- @Override
- protected void onDraw(Canvas canvas) {
- if (mBitmap == null) {
- return;
- }
- canvas.drawBitmap(mBitmap, 0, 0, null);
- }
-
- /**
- * Copy a bitmap of the source view.
- * <p>
- * Callers MUST call {@link #unbind()} when the copy is no longer needed.
- *
- * @param v
- */
- public void bind(View v) {
- unbind();
-
- if (v.getWidth() == 0 || v.getHeight() == 0) {
- return;
- }
-
- try {
- mBitmap = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Config.ARGB_8888);
- v.draw(new Canvas(mBitmap));
- } catch (OutOfMemoryError e) {
- LogUtils.e(LOG_TAG, e, "Unable to create fancy list transition bitmap");
- // mBitmap will just be null, and this won't draw, which is fine
- }
- }
-
- /**
- * Release resources from a previous {@link #bind(View)}.
- */
- public void unbind() {
- if (mBitmap != null) {
- mBitmap.recycle();
- mBitmap = null;
- }
- }
-
-}
diff --git a/src/com/android/mail/ui/ConversationViewFrame.java b/src/com/android/mail/ui/ConversationViewFrame.java
new file mode 100644
index 000000000..73bbf459c
--- /dev/null
+++ b/src/com/android/mail/ui/ConversationViewFrame.java
@@ -0,0 +1,58 @@
+/**
+ * Copyright (C) 2014 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.content.Context;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.widget.FrameLayout;
+
+/**
+ * Empty frame to steal events for two-pane view when the drawer is open.
+ */
+public class ConversationViewFrame extends FrameLayout {
+
+ public interface DownEventListener {
+ boolean onInterceptCVDownEvent();
+ }
+
+ private DownEventListener mDownEventListener;
+
+ public ConversationViewFrame(Context c) {
+ super(c, null);
+ }
+
+ public ConversationViewFrame(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public void setDownEventListener(DownEventListener l) {
+ mDownEventListener = l;
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ boolean steal = false;
+ if (ev.getActionMasked() == MotionEvent.ACTION_DOWN && mDownEventListener != null) {
+ steal = mDownEventListener.onInterceptCVDownEvent();
+ // just drop the event stream that follows when we steal; we closed the drawer and
+ // that's enough.
+ }
+ return steal;
+ }
+
+}
diff --git a/src/com/android/mail/ui/DrawerController.java b/src/com/android/mail/ui/DrawerController.java
index 4b849996f..f752ef507 100644
--- a/src/com/android/mail/ui/DrawerController.java
+++ b/src/com/android/mail/ui/DrawerController.java
@@ -29,5 +29,6 @@ public interface DrawerController {
void unregisterDrawerListener(DrawerLayout.DrawerListener l);
boolean isDrawerOpen();
boolean isDrawerVisible();
+ void toggleDrawerState();
}
diff --git a/src/com/android/mail/ui/FolderListFragment.java b/src/com/android/mail/ui/FolderListFragment.java
index 64537f9b9..17e1789e2 100644
--- a/src/com/android/mail/ui/FolderListFragment.java
+++ b/src/com/android/mail/ui/FolderListFragment.java
@@ -123,6 +123,7 @@ public class FolderListFragment extends ListFragment implements
private FolderSelector mFolderChanger;
/** Object that changes accounts on our behalf */
private AccountController mAccountController;
+ private DrawerController mDrawerController;
/** The currently selected folder (the folder being viewed). This is never null. */
private FolderUri mSelectedFolderUri = FolderUri.EMPTY;
@@ -203,6 +204,10 @@ public class FolderListFragment extends ListFragment implements
private boolean mInboxPresent;
+ private boolean mMiniDrawerEnabled;
+ private boolean mIsMinimized;
+ private MiniDrawerView mMiniDrawerView;
+
/**
* Constructor needs to be public to handle orientation changes and activity lifecycle events.
*/
@@ -289,6 +294,25 @@ public class FolderListFragment extends ListFragment implements
return;
}
mActivity = (ControllableActivity) activity;
+
+ final int avatarSize = getActivity().getResources().getDimensionPixelSize(
+ R.dimen.account_avatar_dimension);
+
+ mImagesCache = new UnrefedBitmapCache(Utils.isLowRamDevice(getActivity()) ?
+ 0 : avatarSize * avatarSize * IMAGE_CACHE_COUNT,
+ AVATAR_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION,
+ AVATAR_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY);
+ mContactResolver = new ContactResolver(getActivity().getContentResolver(),
+ mImagesCache);
+
+ mMiniDrawerView.setController(this);
+ if (!mMiniDrawerEnabled) {
+ mMiniDrawerView.setVisibility(View.GONE);
+ } else {
+ // set up initial state
+ setMinimized(isMinimized());
+ }
+
final FolderController controller = mActivity.getFolderController();
// Listen to folder changes in the future
mFolderObserver = new FolderObserver() {
@@ -350,6 +374,9 @@ public class FolderListFragment extends ListFragment implements
}
mFolderWatcher.updateAccountList(getAllAccounts());
rebuildAccountList();
+ if (mMiniDrawerEnabled) {
+ mMiniDrawerView.refresh();
+ }
}
};
mAllAccountsObserver.initialize(accountController);
@@ -363,6 +390,8 @@ public class FolderListFragment extends ListFragment implements
}
}
+ mDrawerController = mActivity.getDrawerController();
+
if (mActivity.isFinishing()) {
// Activity is finishing, just bail.
return;
@@ -381,16 +410,20 @@ public class FolderListFragment extends ListFragment implements
mFolderWatcher.updateAccountList(getAllAccounts());
setListAdapter(mMergedAdapter);
+ }
- final int avatarSize = getActivity().getResources().getDimensionPixelSize(
- R.dimen.account_avatar_dimension);
+ public BitmapCache getBitmapCache() {
+ return mImagesCache;
+ }
- mImagesCache = new UnrefedBitmapCache(Utils.isLowRamDevice(getActivity()) ?
- 0 : avatarSize * avatarSize * IMAGE_CACHE_COUNT,
- AVATAR_IMAGES_PREVIEWS_CACHE_NON_POOLED_FRACTION,
- AVATAR_IMAGES_PREVIEWS_CACHE_NULL_CAPACITY);
- mContactResolver = new ContactResolver(getActivity().getContentResolver(),
- mImagesCache);
+ public ContactResolver getContactResolver() {
+ return mContactResolver;
+ }
+
+ public void toggleDrawerState() {
+ if (mDrawerController != null) {
+ mDrawerController.toggleDrawerState();
+ }
}
/**
@@ -437,6 +470,8 @@ public class FolderListFragment extends ListFragment implements
mInboxPresent = true;
}
+ mMiniDrawerView = (MiniDrawerView) rootView.findViewById(R.id.mini_drawer);
+
return rootView;
}
@@ -577,27 +612,31 @@ public class FolderListFragment extends ListFragment implements
folder = null;
}
if (folder != null) {
- // Go to the conversation list for this folder.
- if (!folder.folderUri.equals(mSelectedFolderUri)) {
- mNextFolder = folder;
- mAccountController.closeDrawer(true /** hasNewFolderOrAccount */,
- null /** nextAccount */,
- folder /** nextFolder */);
+ final String label = (folderType == DrawerItem.FOLDER_RECENT) ? "recent" : "normal";
+ onFolderSelected(folder, label);
+ }
+ }
- final String label = (folderType == DrawerItem.FOLDER_RECENT) ? "recent" : "normal";
- Analytics.getInstance().sendEvent("switch_folder", folder.getTypeDescription(),
- label, 0);
+ public void onFolderSelected(Folder folder, String analyticsLabel) {
+ // Go to the conversation list for this folder.
+ if (!folder.folderUri.equals(mSelectedFolderUri)) {
+ mNextFolder = folder;
+ mAccountController.closeDrawer(true /** hasNewFolderOrAccount */,
+ null /** nextAccount */,
+ folder /** nextFolder */);
- } else {
- // Clicked on same folder, just close drawer
- mAccountController.closeDrawer(false /** hasNewFolderOrAccount */,
- null /** nextAccount */,
- folder /** nextFolder */);
- }
+ Analytics.getInstance().sendEvent("switch_folder", folder.getTypeDescription(),
+ analyticsLabel, 0);
+
+ } else {
+ // Clicked on same folder, just close drawer
+ mAccountController.closeDrawer(false /** hasNewFolderOrAccount */,
+ null /** nextAccount */,
+ folder /** nextFolder */);
}
}
- protected void onAccountSelected(Account account) {
+ public void onAccountSelected(Account account) {
// Only reset the cache if the account has changed.
if (mCurrentAccount == null || account == null ||
!mCurrentAccount.getEmailAddress().equals(account.getEmailAddress())) {
@@ -616,7 +655,6 @@ public class FolderListFragment extends ListFragment implements
@Override
public Loader<ObjectCursor<Folder>> onCreateLoader(int id, Bundle args) {
- mListView.setEmptyView(null);
final Uri folderListUri;
if (id == FOLDER_LIST_LOADER_ID) {
if (mFolderListUri != null) {
@@ -641,6 +679,11 @@ public class FolderListFragment extends ListFragment implements
if (mFolderAdapter != null) {
if (loader.getId() == FOLDER_LIST_LOADER_ID) {
mFolderAdapter.setCursor(data);
+
+ if (mMiniDrawerEnabled) {
+ mMiniDrawerView.refresh();
+ }
+
} else if (loader.getId() == ALL_FOLDER_LIST_LOADER_ID) {
mFolderAdapter.setAllFolderListCursor(data);
}
@@ -663,7 +706,7 @@ public class FolderListFragment extends ListFragment implements
* frequency of use.
* @return a list of accounts, sorted by frequency of use
*/
- protected Account[] getAllAccounts() {
+ public Account[] getAllAccounts() {
if (mAllAccountsObserver != null) {
return mAllAccountsObserver.getAllAccounts();
}
@@ -681,12 +724,42 @@ public class FolderListFragment extends ListFragment implements
}
}
+ public boolean isMiniDrawerEnabled() {
+ return mMiniDrawerEnabled;
+ }
+
+ public void setMiniDrawerEnabled(boolean enabled) {
+ mMiniDrawerEnabled = enabled;
+ setMinimized(isMinimized()); // init visual state
+ }
+
+ public boolean isMinimized() {
+ return mMiniDrawerEnabled && mIsMinimized;
+ }
+
+ public void setMinimized(boolean minimized) {
+ if (!mMiniDrawerEnabled) {
+ return;
+ }
+
+ mIsMinimized = minimized;
+
+ if (isMinimized()) {
+ mMiniDrawerView.setVisibility(View.VISIBLE);
+ mListView.setVisibility(View.INVISIBLE);
+ } else {
+ mMiniDrawerView.setVisibility(View.INVISIBLE);
+ mListView.setVisibility(View.VISIBLE);
+ }
+ }
+
/**
* Interface for all cursor adapters that allow setting a cursor and being destroyed.
*/
private interface FolderListFragmentCursorAdapter extends ListAdapter {
/** Update the folder list cursor with the cursor given here. */
void setCursor(ObjectCursor<Folder> cursor);
+ ObjectCursor<Folder> getCursor();
/** Update the all folder list cursor with the cursor given here. */
void setAllFolderListCursor(ObjectCursor<Folder> cursor);
/** Remove all observers and destroy the object. */
@@ -987,6 +1060,11 @@ public class FolderListFragment extends ListFragment implements
}
@Override
+ public ObjectCursor<Folder> getCursor() {
+ return mCursor;
+ }
+
+ @Override
public void setAllFolderListCursor(final ObjectCursor<Folder> cursor) {
mAllFolderListCursor = cursor;
rebuildAccountList();
@@ -1089,6 +1167,11 @@ public class FolderListFragment extends ListFragment implements
}
@Override
+ public ObjectCursor<Folder> getCursor() {
+ throw new UnsupportedOperationException("drawers don't have hierarchical folders");
+ }
+
+ @Override
public void setAllFolderListCursor(final ObjectCursor<Folder> cursor) {
// Not necessary in HierarchicalFolderListAdapter
}
@@ -1176,6 +1259,14 @@ public class FolderListFragment extends ListFragment implements
return mMergedAdapter;
}
+ public Account getCurrentAccount() {
+ return mCurrentAccount;
+ }
+
+ public ObjectCursor<Folder> getFoldersCursor() {
+ return (mFolderAdapter != null) ? mFolderAdapter.getCursor() : null;
+ }
+
private class FooterAdapter extends BaseAdapter {
private final List<FooterItem> mFooterItems = Lists.newArrayList();
@@ -1322,6 +1413,13 @@ public class FolderListFragment extends ListFragment implements
// synced.
mSelectedFolderUri = FolderUri.EMPTY;
mCurrentFolderForUnreadCheck = null;
+
+ // also set/update the mini-drawer
+ if (mMiniDrawerEnabled) {
+ //foobar
+ mMiniDrawerView.refresh();
+ }
+
} else if (account == null) {
// This should never happen currently, but is a safeguard against a very incorrect
// non-null account -> null account transition.
diff --git a/src/com/android/mail/ui/MiniDrawerView.java b/src/com/android/mail/ui/MiniDrawerView.java
new file mode 100644
index 000000000..83c8f5d2e
--- /dev/null
+++ b/src/com/android/mail/ui/MiniDrawerView.java
@@ -0,0 +1,202 @@
+/**
+ * Copyright (C) 2014 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.content.Context;
+import android.support.annotation.LayoutRes;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import com.android.mail.R;
+import com.android.mail.bitmap.AccountAvatarDrawable;
+import com.android.mail.content.ObjectCursor;
+import com.android.mail.providers.Account;
+import com.android.mail.providers.Folder;
+import com.google.common.collect.Lists;
+
+import java.util.List;
+
+/**
+ * A smaller version of the account- and folder-switching drawer view for tablet UIs.
+ */
+public class MiniDrawerView extends LinearLayout {
+
+ private FolderListFragment mController;
+ private final int mDrawWidth;
+ // use the same dimen as AccountItemView to participate in recycling
+ // TODO: but Material account switcher doesn't recycle...
+ private final int mAvatarDecodeSize;
+
+ private View mDotdotdot;
+ private View mSpacer;
+
+ private AccountItem mCurrentAccount;
+ private final List<AccountItem> mRecentAccounts = Lists.newArrayList();
+
+ private final LayoutInflater mInflater;
+
+ private static final int NUM_RECENT_ACCOUNTS = 2;
+
+ public MiniDrawerView(Context context) {
+ this(context, null);
+ }
+
+ public MiniDrawerView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mDrawWidth = getResources().getDimensionPixelSize(R.dimen.two_pane_drawer_width_mini);
+ mAvatarDecodeSize = getResources().getDimensionPixelSize(R.dimen.account_avatar_dimension);
+
+ mInflater = LayoutInflater.from(context);
+ }
+
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+
+ mCurrentAccount = new AccountItem((ImageView) findViewById(R.id.current_account_avatar));
+ mSpacer = findViewById(R.id.spacer);
+ mDotdotdot = findViewById(R.id.dotdotdot);
+ mDotdotdot.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mController.toggleDrawerState();
+ }
+ });
+ }
+
+ public void setController(FolderListFragment flf) {
+ mController = flf;
+
+ if (!mController.isMiniDrawerEnabled()) {
+ return;
+ }
+
+ // wait for the controller to set these up
+ mCurrentAccount.setupDrawable();
+ }
+
+ public void refresh() {
+ if (mController == null) {
+ return;
+ }
+
+ final Account currentAccount = mController.getCurrentAccount();
+ if (currentAccount != null) {
+ mCurrentAccount.setAccount(currentAccount);
+ }
+
+ View child;
+
+ // TODO: figure out the N most recent accounts, don't just take the first few
+ final int removePos = indexOfChild(mSpacer) + 1;
+ if (getChildCount() > removePos) {
+ removeViews(removePos, getChildCount() - removePos);
+ }
+ final Account[] accounts = mController.getAllAccounts();
+ int count = 0;
+ for (Account a : accounts) {
+ if (count >= NUM_RECENT_ACCOUNTS) {
+ break;
+ }
+ if (currentAccount.uri.equals(a.uri)) {
+ continue;
+ }
+ final ImageView iv = (ImageView) mInflater.inflate(
+ R.layout.mini_drawer_recent_account_item, this, false /* attachToRoot */);
+ final AccountItem item = new AccountItem(iv);
+ item.setupDrawable();
+ item.setAccount(a);
+ iv.setTag(item);
+ addView(iv);
+ count++;
+ }
+
+ // reset the inbox views for this account
+ while ((child=getChildAt(1)) != mDotdotdot) {
+ removeView(child);
+ }
+ final ObjectCursor<Folder> folderCursor = mController.getFoldersCursor();
+ if (folderCursor != null && !folderCursor.isClosed()) {
+ int pos = -1;
+ int numInboxes = 0;
+ while (folderCursor.moveToPosition(++pos)) {
+ final Folder f = folderCursor.getModel();
+ if (f.isInbox()) {
+ final ImageView iv = (ImageView) mInflater.inflate(
+ R.layout.mini_drawer_folder_item, this, false /* attachToRoot */);
+ iv.setTag(new FolderItem(f, iv));
+ addView(iv, 1 + numInboxes);
+ numInboxes++;
+ }
+ }
+ }
+ }
+
+ private class FolderItem implements View.OnClickListener {
+ public final Folder folder;
+ public final ImageView view;
+
+ public FolderItem(Folder f, ImageView iv) {
+ folder = f;
+ view = iv;
+ Folder.setIcon(folder, view);
+ view.setOnClickListener(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+ mController.onFolderSelected(folder, "mini-drawer");
+ }
+ }
+
+ private class AccountItem implements View.OnClickListener {
+ private Account mAccount;
+ // FIXME: this codepath doesn't use GMS Core, resulting in inconsistent avatars
+ // vs. ownerslib. switch to a generic photo getter+listener interface on FLF
+ // so these drawables are obtainable regardless of how they are loaded.
+ private AccountAvatarDrawable mDrawable;
+ public final ImageView view;
+
+ public AccountItem(ImageView iv) {
+ view = iv;
+ view.setOnClickListener(this);
+ }
+
+ public void setupDrawable() {
+ mDrawable = new AccountAvatarDrawable(getResources(),
+ mController.getBitmapCache(), mController.getContactResolver());
+ mDrawable.setDecodeDimensions(mAvatarDecodeSize, mAvatarDecodeSize);
+ view.setImageDrawable(mDrawable);
+ }
+
+ public void setAccount(Account acct) {
+ mAccount = acct;
+ mDrawable.bind(mAccount.getSenderName(), mAccount.getEmailAddress());
+ }
+
+ @Override
+ public void onClick(View v) {
+ mController.onAccountSelected(mAccount);
+ }
+
+ }
+
+}
diff --git a/src/com/android/mail/ui/OnePaneController.java b/src/com/android/mail/ui/OnePaneController.java
index 4a9a91611..dc3a8ae36 100644
--- a/src/com/android/mail/ui/OnePaneController.java
+++ b/src/com/android/mail/ui/OnePaneController.java
@@ -26,6 +26,7 @@ import android.os.Bundle;
import android.support.annotation.LayoutRes;
import android.support.v4.widget.DrawerLayout;
import android.view.Gravity;
+import android.view.View;
import android.widget.ListView;
import com.android.mail.ConversationListContext;
@@ -140,6 +141,9 @@ public final class OnePaneController extends AbstractActivityController {
mDrawerPullout = mDrawerContainer.findViewWithTag(drawerPulloutTag);
mDrawerPullout.setBackgroundResource(R.color.list_background_color);
+ // CV is initially GONE on 1-pane (mode changes trigger visibility changes)
+ mActivity.findViewById(R.id.conversation_pager).setVisibility(View.GONE);
+
// The parent class sets the correct viewmode and starts the application off.
return super.onCreate(savedInstanceState);
}
diff --git a/src/com/android/mail/ui/TwoPaneController.java b/src/com/android/mail/ui/TwoPaneController.java
index bfe2aac67..4f52610c1 100644
--- a/src/com/android/mail/ui/TwoPaneController.java
+++ b/src/com/android/mail/ui/TwoPaneController.java
@@ -17,6 +17,7 @@
package com.android.mail.ui;
+import android.app.ActionBar;
import android.app.Activity;
import android.app.Fragment;
import android.app.FragmentManager;
@@ -24,8 +25,7 @@ import android.app.FragmentTransaction;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.LayoutRes;
-import android.support.v4.widget.DrawerLayout;
-import android.view.Gravity;
+import android.view.View;
import android.widget.ListView;
import com.android.mail.ConversationListContext;
@@ -41,16 +41,30 @@ import com.android.mail.utils.Utils;
* Controller for two-pane Mail activity. Two Pane is used for tablets, where screen real estate
* abounds.
*/
-public final class TwoPaneController extends AbstractActivityController {
+public final class TwoPaneController extends AbstractActivityController implements
+ ConversationViewFrame.DownEventListener {
private static final String SAVED_MISCELLANEOUS_VIEW = "saved-miscellaneous-view";
private static final String SAVED_MISCELLANEOUS_VIEW_TRANSACTION_ID =
"saved-miscellaneous-view-transaction-id";
private TwoPaneLayout mLayout;
+ @Deprecated
private Conversation mConversationToShow;
/**
+ * 2-pane, in wider configurations, allows peeking at a conversation view without having the
+ * conversation marked-as-read as far as read/unread state goes.<br>
+ * <br>
+ * This flag applies to {@link AbstractActivityController#mCurrentConversation} and indicates
+ * that the current conversation, if set, is in a 'peeking' state. If there is no current
+ * conversation, peeking is implied (in certain view configurations) and this value is
+ * meaningless.
+ */
+ // TODO: save in instance state
+ private boolean mCurrentConversationJustPeeking;
+
+ /**
* Used to determine whether onViewModeChanged should skip a potential
* fragment transaction that would remove a miscellaneous view.
*/
@@ -60,6 +74,15 @@ public final class TwoPaneController extends AbstractActivityController {
super(activity, viewMode);
}
+ public boolean isCurrentConversationJustPeeking() {
+ return mCurrentConversationJustPeeking;
+ }
+
+ private boolean isConversationOnlyMode() {
+ return getCurrentConversation() != null && !isCurrentConversationJustPeeking()
+ && !mLayout.shouldShowPreviewPanel();
+ }
+
/**
* Display the conversation list fragment.
*/
@@ -121,10 +144,6 @@ public final class TwoPaneController extends AbstractActivityController {
@Override
public boolean onCreate(Bundle savedState) {
- mDrawerContainer = (DrawerLayout) mActivity.findViewById(R.id.drawer_container);
- mDrawerContainer.setDrawerTitle(Gravity.START,
- mActivity.getActivityContext().getString(R.string.drawer_title));
- mDrawerPullout = mDrawerContainer.findViewById(R.id.content_pane);
mLayout = (TwoPaneLayout) mActivity.findViewById(R.id.two_pane_activity);
if (mLayout == null) {
// We need the layout for everything. Crash/Return early if it is null.
@@ -132,7 +151,11 @@ public final class TwoPaneController extends AbstractActivityController {
return false;
}
mLayout.setController(this, Intent.ACTION_SEARCH.equals(mActivity.getIntent().getAction()));
- mLayout.setDrawerLayout(mDrawerContainer);
+ mActivity.getWindow().setBackgroundDrawable(null);
+
+ final FolderListFragment flf = getFolderListFragment();
+ flf.setMiniDrawerEnabled(true);
+ flf.setMinimized(true);
if (savedState != null) {
mSavedMiscellaneousView = savedState.getBoolean(SAVED_MISCELLANEOUS_VIEW, false);
@@ -197,14 +220,27 @@ public final class TwoPaneController extends AbstractActivityController {
mViewMode.enterConversationListMode();
}
- if (!Utils.isEmpty(folder.parent)) {
- // Show the up affordance when digging into child folders.
- mActionBarController.setBackButton();
- }
setHierarchyFolder(folder);
super.onFolderSelected(folder);
}
+ public boolean isDrawerOpen() {
+ final FolderListFragment flf = getFolderListFragment();
+ return flf != null && !flf.isMinimized();
+ }
+
+ @Override
+ protected void toggleDrawerState() {
+ final FolderListFragment flf = getFolderListFragment();
+ if (flf == null) {
+ LogUtils.w(LOG_TAG, "no drawer to toggle open/closed");
+ return;
+ }
+ flf.setMinimized(!flf.isMinimized());
+ mLayout.requestLayout();
+ resetActionBarIcon();
+ }
+
@Override
public void onViewModeChanged(int newMode) {
if (!mSavedMiscellaneousView && mMiscellaneousViewTransactionId >= 0) {
@@ -216,6 +252,9 @@ public final class TwoPaneController extends AbstractActivityController {
mSavedMiscellaneousView = false;
super.onViewModeChanged(newMode);
+ if (!isConversationOnlyMode()) {
+ mFloatingComposeButton.setVisibility(View.VISIBLE);
+ }
if (newMode != ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) {
// Clear the wait fragment
hideWaitForInitialization();
@@ -251,16 +290,15 @@ public final class TwoPaneController extends AbstractActivityController {
@Override
public void resetActionBarIcon() {
- if (isDrawerEnabled()) {
- return;
- }
- // On two-pane, the back button is only removed in the conversation list mode for top level
- // folders, and shown for every other condition.
- if ((mViewMode.isListMode() && (Folder.isRoot(mFolder) || mFolder.parent == null))
- || mViewMode.isWaitingForSync()) {
- mActionBarController.removeBackButton();
+ final ActionBar ab = mActivity.getActionBar();
+ final boolean isChildFolder = getFolder() != null && !Utils.isEmpty(getFolder().parent);
+ if (isConversationOnlyMode() || isChildFolder) {
+ ab.setHomeAsUpIndicator(R.drawable.ic_arrow_back_wht_24dp);
+ ab.setHomeActionContentDescription(0 /* system default */);
} else {
- mActionBarController.setBackButton();
+ ab.setHomeAsUpIndicator(R.drawable.ic_drawer);
+ ab.setHomeActionContentDescription(
+ isDrawerOpen() ? R.string.drawer_close : R.string.drawer_open);
}
}
@@ -317,6 +355,11 @@ public final class TwoPaneController extends AbstractActivityController {
// ViewMode.CONVERSATION and yet the conversation list goes in and out of visibility.
enableOrDisableCab();
+ // close the drawer, if open
+ if (isDrawerOpen()) {
+ toggleDrawerState();
+ }
+
// When a mode change is required, wait for onConversationVisibilityChanged(), the signal
// that the mode change animation has finished, before rendering the conversation.
mConversationToShow = conversation;
@@ -394,28 +437,12 @@ public final class TwoPaneController extends AbstractActivityController {
*/
@Override
public boolean handleUpPress() {
- int mode = mViewMode.getMode();
- if (mode == ViewMode.CONVERSATION || mViewMode.isAdMode()) {
+ if (isConversationOnlyMode()) {
handleBackPress();
- } else if (mode == ViewMode.SEARCH_RESULTS_CONVERSATION) {
- if (mLayout.isConversationListCollapsed()
- || (ConversationListContext.isSearchResult(mConvListContext) && !Utils.
- showTwoPaneSearchResults(mActivity.getApplicationContext()))) {
- handleBackPress();
- } else {
- mActivity.finish();
- }
- } else if (mode == ViewMode.SEARCH_RESULTS_LIST) {
- mActivity.finish();
- } else if (mode == ViewMode.CONVERSATION_LIST
- || mode == ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION) {
- if (Folder.isRoot(mFolder)) {
- // Show the drawer
- toggleDrawerState();
- } else {
- popView(true);
- }
+ } else {
+ toggleDrawerState();
}
+
return true;
}
@@ -423,7 +450,11 @@ public final class TwoPaneController extends AbstractActivityController {
public boolean handleBackPress() {
// Clear any visible undo bars.
mToastBar.hide(false, false /* actionClicked */);
- popView(false);
+ if (isDrawerOpen()) {
+ toggleDrawerState();
+ } else {
+ popView(false);
+ }
return true;
}
@@ -458,22 +489,9 @@ public final class TwoPaneController extends AbstractActivityController {
// inbox. This fixes b/9006969 so that on smaller tablets where we have this
// hybrid one and two-pane mode, we will return to the inbox. On larger tablets,
// we will instead exit the app.
- } else {
- // Don't think mLayout could be null but checking just in case
- if (mLayout == null) {
- LogUtils.wtf(LOG_TAG, new Throwable(), "mLayout is null");
- }
- // mFolder could be null if back is pressed while account is waiting for sync
- final boolean shouldLoadInbox = mode == ViewMode.CONVERSATION_LIST &&
- mFolder != null &&
- !mFolder.folderUri.equals(mAccount.settings.defaultInbox) &&
- mLayout != null && !mLayout.isExpansiveLayout();
- if (shouldLoadInbox) {
- loadAccountInbox();
- } else if (!preventClose) {
- // There is nothing else to pop off the stack.
- mActivity.finish();
- }
+ } else if (!preventClose) {
+ // There is nothing else to pop off the stack.
+ mActivity.finish();
}
}
}
@@ -538,7 +556,8 @@ public final class TwoPaneController extends AbstractActivityController {
@Override
public boolean isDrawerEnabled() {
- return mLayout.isDrawerEnabled();
+ // two-pane has its own drawer-like thing that expands inline from a minimized state.
+ return false;
}
@Override
@@ -566,4 +585,14 @@ public final class TwoPaneController extends AbstractActivityController {
getConversationListFragment().setRawSelected(selectPosition, true);
}
}
+
+ @Override
+ public boolean onInterceptCVDownEvent() {
+ // handle a down event on CV by closing the drawer if open
+ if (isDrawerOpen()) {
+ toggleDrawerState();
+ return true;
+ }
+ return false;
+ }
}
diff --git a/src/com/android/mail/ui/TwoPaneLayout.java b/src/com/android/mail/ui/TwoPaneLayout.java
index f70c9d5ae..5af59ccff 100644
--- a/src/com/android/mail/ui/TwoPaneLayout.java
+++ b/src/com/android/mail/ui/TwoPaneLayout.java
@@ -23,12 +23,9 @@ import android.animation.TimeInterpolator;
import android.app.Activity;
import android.content.Context;
import android.content.res.Resources;
-import android.support.v4.widget.DrawerLayout;
import android.util.AttributeSet;
-import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewParent;
import android.view.animation.AnimationUtils;
import android.widget.FrameLayout;
@@ -65,13 +62,17 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
private static final String LOG_TAG = "TwoPaneLayout";
private static final long SLIDE_DURATION_MS = 300;
+ private final int mDrawerWidthMini;
+ private final int mDrawerWidthOpen;
private final double mConversationListWeight;
- private final double mFolderListWeight;
private final TimeInterpolator mSlideInterpolator;
/**
- * True if and only if the conversation list is collapsible in the current device configuration.
- * See {@link #isConversationListCollapsed()} to see whether it is currently collapsed
- * (based on the current view mode).
+ * If true, this layout group will treat the thread list and conversation view as full-width
+ * panes to switch between.<br>
+ * <br>
+ * If false, always show a conversation view right next to the conversation list. This view will
+ * also be populated (preview / "peek" mode) with a default conversation if none is selected by
+ * the user.
*/
private final boolean mListCollapsible;
@@ -86,12 +87,10 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
*/
private int mPositionedMode = ViewMode.UNKNOWN;
- private AbstractActivityController mController;
+ private TwoPaneController mController;
private LayoutListener mListener;
private boolean mIsSearchResult;
- private DrawerLayout mDrawerLayout;
-
private View mMiscellaneousView;
private View mConversationView;
private View mFoldersView;
@@ -105,25 +104,6 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
onTransitionComplete();
}
};
- /**
- * A special view used during animation of the conversation list.
- * <p>
- * The conversation list changes width when switching view modes, so to visually smooth out
- * the transition, we cross-fade the old and new widths. During the transition, a bitmap of the
- * old conversation list is kept here, and this view moves in tandem with the real list view,
- * but its opacity gradually fades out to give way to the new width.
- */
- private ConversationListCopy mListCopyView;
-
- /**
- * During a mode transition, this value is the final width for {@link #mListCopyView}. We want
- * to avoid changing its width during the animation, as it should match the initial width of
- * {@link #mListView}.
- */
- private Integer mListCopyWidthOnComplete;
-
- private final boolean mIsExpansiveLayout;
- private boolean mDrawerInitialSetupComplete;
public TwoPaneLayout(Context context) {
this(context, null);
@@ -139,28 +119,24 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
// in the constants
mListCollapsible = res.getBoolean(R.bool.list_collapsible);
+ mDrawerWidthMini = res.getDimensionPixelSize(R.dimen.two_pane_drawer_width_mini);
+ mDrawerWidthOpen = res.getDimensionPixelSize(R.dimen.two_pane_drawer_width_open);
+
mSlideInterpolator = AnimationUtils.loadInterpolator(context,
android.R.interpolator.decelerate_cubic);
- final int folderListWeight = res.getInteger(R.integer.folder_list_weight);
final int convListWeight = res.getInteger(R.integer.conversation_list_weight);
final int convViewWeight = res.getInteger(R.integer.conversation_view_weight);
- mFolderListWeight = (double) folderListWeight
- / (folderListWeight + convListWeight);
mConversationListWeight = (double) convListWeight
/ (convListWeight + convViewWeight);
-
- mIsExpansiveLayout = res.getBoolean(R.bool.use_expansive_tablet_ui);
- mDrawerInitialSetupComplete = false;
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
- mFoldersView = findViewById(R.id.content_pane);
+ mFoldersView = findViewById(R.id.drawer);
mListView = findViewById(R.id.conversation_list_pane);
- mListCopyView = (ConversationListCopy) findViewById(R.id.conversation_list_copy);
mConversationView = findViewById(R.id.conversation_pane);
mMiscellaneousView = findViewById(MISCELLANEOUS_VIEW_ID);
@@ -168,20 +144,17 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
mCurrentMode = ViewMode.UNKNOWN;
mFoldersView.setVisibility(GONE);
mListView.setVisibility(GONE);
- mListCopyView.setVisibility(GONE);
mConversationView.setVisibility(GONE);
mMiscellaneousView.setVisibility(GONE);
}
@VisibleForTesting
- public void setController(AbstractActivityController controller, boolean isSearchResult) {
+ public void setController(TwoPaneController controller, boolean isSearchResult) {
mController = controller;
mListener = controller;
mIsSearchResult = isSearchResult;
- }
- public void setDrawerLayout(DrawerLayout drawerLayout) {
- mDrawerLayout = drawerLayout;
+ ((ConversationViewFrame) mConversationView).setDownEventListener(mController);
}
@Override
@@ -194,9 +167,7 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
LogUtils.d(Utils.VIEW_DEBUGGING_TAG, "TPL(%s).onLayout()", this);
- if (changed || mCurrentMode != mPositionedMode) {
- positionPanes(getMeasuredWidth());
- }
+ positionPanes(getMeasuredWidth());
super.onLayout(changed, l, t, r, b);
}
@@ -207,52 +178,12 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
* @param parentWidth this view's new width
*/
private void setupPaneWidths(int parentWidth) {
- final int foldersWidth = computeFolderListWidth(parentWidth);
- final int foldersFragmentWidth;
- if (isDrawerView(mFoldersView)) {
- foldersFragmentWidth = getResources().getDimensionPixelSize(R.dimen.drawer_width);
- } else {
- foldersFragmentWidth = foldersWidth;
- }
- final int convWidth = computeConversationWidth(parentWidth);
-
- setPaneWidth(mFoldersView, foldersFragmentWidth);
-
- // only adjust the fixed conversation view width when my width changes
+ // only adjust the pane widths when my width changes
if (parentWidth != getMeasuredWidth()) {
- LogUtils.i(LOG_TAG, "setting up new TPL, w=%d fw=%d cv=%d", parentWidth,
- foldersWidth, convWidth);
-
+ final int convWidth = computeConversationWidth(parentWidth);
setPaneWidth(mMiscellaneousView, convWidth);
setPaneWidth(mConversationView, convWidth);
- }
-
- final int currListWidth = getPaneWidth(mListView);
- int listWidth = currListWidth;
- switch (mCurrentMode) {
- case ViewMode.AD:
- case ViewMode.CONVERSATION:
- case ViewMode.SEARCH_RESULTS_CONVERSATION:
- if (!mListCollapsible) {
- listWidth = parentWidth - convWidth;
- }
- break;
- case ViewMode.CONVERSATION_LIST:
- case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
- case ViewMode.SEARCH_RESULTS_LIST:
- listWidth = parentWidth - foldersWidth;
- break;
- default:
- break;
- }
- LogUtils.d(LOG_TAG, "conversation list width change, w=%d", listWidth);
- setPaneWidth(mListView, listWidth);
-
- if ((mCurrentMode != mPositionedMode && mPositionedMode != ViewMode.UNKNOWN)
- || mListCopyWidthOnComplete != null) {
- mListCopyWidthOnComplete = listWidth;
- } else {
- setPaneWidth(mListCopyView, listWidth);
+ setPaneWidth(mListView, computeConversationListWidth(parentWidth));
}
}
@@ -263,86 +194,61 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
* @param width
*/
private void positionPanes(int width) {
- if (mPositionedMode == mCurrentMode) {
- return;
- }
-
- boolean hasPositions = false;
- int convX = 0, listX = 0, foldersX = 0;
+ final int convX, listX, foldersX;
final boolean isRtl = ViewUtils.isViewRtl(this);
- switch (mCurrentMode) {
- case ViewMode.AD:
- case ViewMode.CONVERSATION:
- case ViewMode.SEARCH_RESULTS_CONVERSATION: {
- final int foldersW = getPaneWidth(mFoldersView);
- final int listW = getPaneWidth(mListView);
-
- if (mListCollapsible) {
- if (isRtl) {
- convX = 0;
- listX = width;
- foldersX = width + listW;
- } else {
- convX = 0;
- listX = -listW;
- foldersX = listX - foldersW;
- }
+ final int foldersW = isDrawerOpen() ? mDrawerWidthOpen : mDrawerWidthMini;
+ final int listW = getPaneWidth(mListView);
+
+ if (!mListCollapsible) {
+ if (isRtl) {
+ foldersX = width - mDrawerWidthOpen;
+ listX = width - foldersW - listW;
+ convX = listX - getPaneWidth(mConversationView);
+ } else {
+ foldersX = 0;
+ listX = foldersW;
+ convX = listX + listW;
+ }
+ } else {
+ if (mController.getCurrentConversation() != null
+ && !mController.isCurrentConversationJustPeeking()) {
+ // CV mode
+ if (isRtl) {
+ convX = 0;
+ listX = getPaneWidth(mConversationView);
+ foldersX = listX + width - mDrawerWidthOpen;
} else {
- if (isRtl) {
- convX = 0;
- listX = getPaneWidth(mConversationView);
- foldersX = width;
- } else {
- convX = listW;
- listX = 0;
- foldersX = -foldersW;
- }
+ convX = 0;
+ listX = -listW;
+ foldersX = listX - foldersW;
}
- hasPositions = true;
- LogUtils.i(LOG_TAG, "conversation mode layout, x=%d/%d/%d", foldersX, listX, convX);
- break;
- }
- case ViewMode.CONVERSATION_LIST:
- case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
- case ViewMode.SEARCH_RESULTS_LIST: {
+ } else {
+ // TL mode
if (isRtl) {
- convX = -getPaneWidth(mConversationView);
- listX = 0;
- foldersX = getPaneWidth(mListView);
+ foldersX = width - mDrawerWidthOpen;
+ listX = width - foldersW - listW;
+ convX = listX - getPaneWidth(mConversationView);
} else {
- convX = width;
- listX = getPaneWidth(mFoldersView);
foldersX = 0;
+ listX = foldersW;
+ convX = listX + listW;
}
-
- hasPositions = true;
- LogUtils.i(LOG_TAG, "conv-list mode layout, fX:%d/lX:%d/cX:%d",
- foldersX, listX, convX);
- break;
}
- default:
- break;
- }
-
- if (hasPositions) {
- animatePanes(foldersX, listX, convX);
}
+ animatePanes(foldersX, listX, convX);
mPositionedMode = mCurrentMode;
}
private final AnimatorListenerAdapter mPaneAnimationListener = new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animation) {
- mListCopyView.unbind();
useHardwareLayer(false);
- fixupListCopyWidth();
onTransitionComplete();
}
@Override
public void onAnimationCancel(Animator animation) {
- mListCopyView.unbind();
useHardwareLayer(false);
}
};
@@ -355,9 +261,7 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
mConversationView.setX(convX);
mMiscellaneousView.setX(convX);
mListView.setX(listX);
- if (!isDrawerView(mFoldersView)) {
- mFoldersView.setX(foldersX);
- }
+ mFoldersView.setX(foldersX);
// listeners need to know that the "transition" is complete, even if one is not run.
// defer notifying listeners because we're in a layout pass, and they might do layout.
@@ -365,17 +269,6 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
return;
}
- final boolean useListCopy = getPaneWidth(mListView) != getPaneWidth(mListCopyView);
-
- if (useListCopy) {
- // freeze the current list view before it gets redrawn
- mListCopyView.bind(mListView);
- mListCopyView.setX(mListView.getX());
-
- mListCopyView.setAlpha(1.0f);
- mListView.setAlpha(0.0f);
- }
-
useHardwareLayer(true);
if (ViewMode.isAdMode(mCurrentMode)) {
@@ -384,25 +277,15 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
mConversationView.animate().x(convX);
}
- if (!isDrawerView(mFoldersView)) {
- mFoldersView.animate().x(foldersX);
- }
- if (useListCopy) {
- mListCopyView.animate().x(listX).alpha(0.0f);
- }
+ mFoldersView.animate().x(foldersX);
mListView.animate()
.x(listX)
- .alpha(1.0f)
.setListener(mPaneAnimationListener);
- configureAnimations(mConversationView, mFoldersView, mListView, mListCopyView,
- mMiscellaneousView);
+ configureAnimations(mConversationView, mFoldersView, mListView, mMiscellaneousView);
}
private void configureAnimations(View... views) {
for (View v : views) {
- if (isDrawerView(v)) {
- continue;
- }
v.animate()
.setInterpolator(mSlideInterpolator)
.setDuration(SLIDE_DURATION_MS);
@@ -411,38 +294,20 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
private void useHardwareLayer(boolean useHardware) {
final int layerType = useHardware ? LAYER_TYPE_HARDWARE : LAYER_TYPE_NONE;
- if (!isDrawerView(mFoldersView)) {
- mFoldersView.setLayerType(layerType, null);
- }
+ mFoldersView.setLayerType(layerType, null);
mListView.setLayerType(layerType, null);
- mListCopyView.setLayerType(layerType, null);
mConversationView.setLayerType(layerType, null);
mMiscellaneousView.setLayerType(layerType, null);
if (useHardware) {
// these buildLayer calls are safe because layout is the only way we get here
// (i.e. these views must already be attached)
- if (!isDrawerView(mFoldersView)) {
- mFoldersView.buildLayer();
- }
+ mFoldersView.buildLayer();
mListView.buildLayer();
- mListCopyView.buildLayer();
mConversationView.buildLayer();
mMiscellaneousView.buildLayer();
}
}
- private void fixupListCopyWidth() {
- if (mListCopyWidthOnComplete == null ||
- getPaneWidth(mListCopyView) == mListCopyWidthOnComplete) {
- mListCopyWidthOnComplete = null;
- return;
- }
- LogUtils.i(LOG_TAG, "onAnimationEnd of list view, setting copy width to %d",
- mListCopyWidthOnComplete);
- setPaneWidth(mListCopyView, mListCopyWidthOnComplete);
- mListCopyWidthOnComplete = null;
- }
-
private void onTransitionComplete() {
if (mController.isDestroyed()) {
// quit early if the hosting activity was destroyed before the animation finished
@@ -483,18 +348,9 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
/**
* Computes the width of the conversation list in stable state of the current mode.
*/
- private int computeConversationListWidth(int totalWidth) {
- switch (mCurrentMode) {
- case ViewMode.CONVERSATION_LIST:
- case ViewMode.WAITING_FOR_ACCOUNT_INITIALIZATION:
- case ViewMode.SEARCH_RESULTS_LIST:
- return totalWidth - computeFolderListWidth(totalWidth);
- case ViewMode.AD:
- case ViewMode.CONVERSATION:
- case ViewMode.SEARCH_RESULTS_CONVERSATION:
- return (int) (totalWidth * mConversationListWeight);
- }
- return 0;
+ private int computeConversationListWidth(int parentWidth) {
+ final int availWidth = parentWidth - mDrawerWidthMini;
+ return mListCollapsible ? availWidth : (int) (availWidth * mConversationListWeight);
}
public int computeConversationWidth() {
@@ -505,25 +361,9 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
* Computes the width of the conversation pane in stable state of the
* current mode.
*/
- private int computeConversationWidth(int totalWidth) {
- if (mListCollapsible) {
- return totalWidth;
- } else {
- return totalWidth - (int) (totalWidth * mConversationListWeight);
- }
- }
-
- /**
- * Computes the width of the folder list in stable state of the current mode.
- */
- private int computeFolderListWidth(int parentWidth) {
- if (mIsSearchResult) {
- return 0;
- } else if (isDrawerView(mFoldersView)) {
- return 0;
- } else {
- return (int) (parentWidth * mFolderListWeight);
- }
+ private int computeConversationWidth(int parentWidth) {
+ return mListCollapsible ? parentWidth :
+ parentWidth - computeConversationListWidth(parentWidth) - mDrawerWidthMini;
}
private void dispatchConversationListVisibilityChange(boolean visible) {
@@ -540,16 +380,17 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
// does not apply to drawer children. will return zero for those.
private int getPaneWidth(View pane) {
- return isDrawerView(pane) ? 0 : pane.getLayoutParams().width;
+ return pane.getLayoutParams().width;
}
- private boolean isDrawerView(View child) {
- return child != null && child.getParent() == mDrawerLayout;
+ private boolean isDrawerOpen() {
+ return mController != null && mController.isDrawerOpen();
}
/**
* @return Whether or not the conversation list is visible on screen.
*/
+ @Deprecated
public boolean isConversationListCollapsed() {
return !ViewMode.isListMode(mCurrentMode) && mListCollapsible;
}
@@ -560,7 +401,6 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
if (mCurrentMode == ViewMode.UNKNOWN) {
mFoldersView.setVisibility(VISIBLE);
mListView.setVisibility(VISIBLE);
- mListCopyView.setVisibility(VISIBLE);
}
if (ViewMode.isAdMode(newMode)) {
@@ -571,34 +411,11 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
mMiscellaneousView.setVisibility(GONE);
}
- // set up the drawer as appropriate for the configuration
- final ViewParent foldersParent = mFoldersView.getParent();
- if (mIsExpansiveLayout && foldersParent != this) {
- if (foldersParent != mDrawerLayout) {
- throw new IllegalStateException("invalid Folders fragment parent: " +
- foldersParent);
- }
- mDrawerLayout.removeView(mFoldersView);
- addView(mFoldersView, 0);
- mFoldersView.findViewById(R.id.folders_pane_edge).setVisibility(VISIBLE);
- mFoldersView.setBackgroundDrawable(null);
- } else if (!mIsExpansiveLayout && foldersParent == this) {
- removeView(mFoldersView);
- mDrawerLayout.addView(mFoldersView);
- final DrawerLayout.LayoutParams lp =
- (DrawerLayout.LayoutParams) mFoldersView.getLayoutParams();
- lp.gravity = Gravity.START;
- mFoldersView.setLayoutParams(lp);
- mFoldersView.findViewById(R.id.folders_pane_edge).setVisibility(GONE);
- mFoldersView.setBackgroundResource(R.color.list_background_color);
- }
-
// detach the pager immediately from its data source (to prevent processing updates)
if (ViewMode.isConversationMode(mCurrentMode)) {
mController.disablePagerUpdates();
}
- mDrawerInitialSetupComplete = true;
mCurrentMode = newMode;
LogUtils.i(LOG_TAG, "onViewModeChanged(%d)", newMode);
@@ -635,11 +452,8 @@ final class TwoPaneLayout extends FrameLayout implements ModeChangeListener {
}
}
- public boolean isDrawerEnabled() {
- return !mIsExpansiveLayout && mDrawerInitialSetupComplete;
+ public boolean shouldShowPreviewPanel() {
+ return !mListCollapsible;
}
- public boolean isExpansiveLayout() {
- return mIsExpansiveLayout;
- }
}
diff --git a/src/com/android/mail/utils/Utils.java b/src/com/android/mail/utils/Utils.java
index 068b4db0e..97b0c938d 100644
--- a/src/com/android/mail/utils/Utils.java
+++ b/src/com/android/mail/utils/Utils.java
@@ -267,6 +267,9 @@ public class Utils {
/**
* @return <code>true</code> if the right edge effect should be displayed on list items
*/
+ @Deprecated
+ // TODO: remove this now that visual design no longer has right-edge caret (which made it so
+ // the hard right edge was drawn IN list items to ensure the caret didn't get an edge)
public static boolean getDisplayListRightEdgeEffect(final boolean tabletDevice,
final boolean listCollapsible, final int viewMode) {
return tabletDevice && !listCollapsible