diff options
41 files changed, 620 insertions, 82 deletions
diff --git a/res/drawable-hdpi/ic_drawer_feedback.png b/res/drawable-hdpi/ic_drawer_feedback.png Binary files differdeleted file mode 100644 index c2221e9c3..000000000 --- a/res/drawable-hdpi/ic_drawer_feedback.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_drawer_help.png b/res/drawable-hdpi/ic_drawer_help.png Binary files differdeleted file mode 100644 index 47d0e2636..000000000 --- a/res/drawable-hdpi/ic_drawer_help.png +++ /dev/null diff --git a/res/drawable-hdpi/ic_menu_feedback.png b/res/drawable-hdpi/ic_menu_feedback.png Binary files differnew file mode 100644 index 000000000..9ba3c59ee --- /dev/null +++ b/res/drawable-hdpi/ic_menu_feedback.png diff --git a/res/drawable-hdpi/ic_menu_help.png b/res/drawable-hdpi/ic_menu_help.png Binary files differnew file mode 100644 index 000000000..88691a247 --- /dev/null +++ b/res/drawable-hdpi/ic_menu_help.png diff --git a/res/drawable-hdpi/ic_menu_settings.png b/res/drawable-hdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 000000000..5ab4391dd --- /dev/null +++ b/res/drawable-hdpi/ic_menu_settings.png diff --git a/res/drawable-mdpi/ic_drawer_feedback.png b/res/drawable-mdpi/ic_drawer_feedback.png Binary files differdeleted file mode 100644 index 371c40942..000000000 --- a/res/drawable-mdpi/ic_drawer_feedback.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_drawer_help.png b/res/drawable-mdpi/ic_drawer_help.png Binary files differdeleted file mode 100644 index c98e7c5d0..000000000 --- a/res/drawable-mdpi/ic_drawer_help.png +++ /dev/null diff --git a/res/drawable-mdpi/ic_menu_feedback.png b/res/drawable-mdpi/ic_menu_feedback.png Binary files differnew file mode 100644 index 000000000..9db3a62f0 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_feedback.png diff --git a/res/drawable-mdpi/ic_menu_help.png b/res/drawable-mdpi/ic_menu_help.png Binary files differnew file mode 100644 index 000000000..787fddcf7 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_help.png diff --git a/res/drawable-mdpi/ic_menu_settings.png b/res/drawable-mdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 000000000..c4f723a80 --- /dev/null +++ b/res/drawable-mdpi/ic_menu_settings.png diff --git a/res/drawable-xhdpi/ic_drawer_feedback.png b/res/drawable-xhdpi/ic_drawer_feedback.png Binary files differdeleted file mode 100644 index 25abb5715..000000000 --- a/res/drawable-xhdpi/ic_drawer_feedback.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_drawer_help.png b/res/drawable-xhdpi/ic_drawer_help.png Binary files differdeleted file mode 100644 index 0db695820..000000000 --- a/res/drawable-xhdpi/ic_drawer_help.png +++ /dev/null diff --git a/res/drawable-xhdpi/ic_menu_feedback.png b/res/drawable-xhdpi/ic_menu_feedback.png Binary files differnew file mode 100644 index 000000000..76f511bfa --- /dev/null +++ b/res/drawable-xhdpi/ic_menu_feedback.png diff --git a/res/drawable-xhdpi/ic_menu_help.png b/res/drawable-xhdpi/ic_menu_help.png Binary files differnew file mode 100644 index 000000000..24e913e02 --- /dev/null +++ b/res/drawable-xhdpi/ic_menu_help.png diff --git a/res/drawable-xhdpi/ic_menu_settings.png b/res/drawable-xhdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 000000000..13cbb9260 --- /dev/null +++ b/res/drawable-xhdpi/ic_menu_settings.png diff --git a/res/drawable-xxhdpi/ic_drawer_feedback.png b/res/drawable-xxhdpi/ic_drawer_feedback.png Binary files differdeleted file mode 100644 index c447252e0..000000000 --- a/res/drawable-xxhdpi/ic_drawer_feedback.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_drawer_help.png b/res/drawable-xxhdpi/ic_drawer_help.png Binary files differdeleted file mode 100644 index 8a7ebac64..000000000 --- a/res/drawable-xxhdpi/ic_drawer_help.png +++ /dev/null diff --git a/res/drawable-xxhdpi/ic_menu_feedback.png b/res/drawable-xxhdpi/ic_menu_feedback.png Binary files differnew file mode 100644 index 000000000..f996c118e --- /dev/null +++ b/res/drawable-xxhdpi/ic_menu_feedback.png diff --git a/res/drawable-xxhdpi/ic_menu_help.png b/res/drawable-xxhdpi/ic_menu_help.png Binary files differnew file mode 100644 index 000000000..2b2252149 --- /dev/null +++ b/res/drawable-xxhdpi/ic_menu_help.png diff --git a/res/drawable-xxhdpi/ic_menu_settings.png b/res/drawable-xxhdpi/ic_menu_settings.png Binary files differnew file mode 100644 index 000000000..95a310f53 --- /dev/null +++ b/res/drawable-xxhdpi/ic_menu_settings.png diff --git a/res/drawable/shadow_top.xml b/res/drawable/shadow_top.xml new file mode 100644 index 000000000..1863ec6ed --- /dev/null +++ b/res/drawable/shadow_top.xml @@ -0,0 +1,24 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> + +<shape xmlns:android="http://schemas.android.com/apk/res/android"> + <gradient + android:angle="90" + android:startColor="#22000000" + android:endColor="@android:color/transparent" /> +</shape> diff --git a/res/layout/drawer_footer_item.xml b/res/layout/drawer_footer_item.xml index 69bd4a8f9..d890bcfff 100644 --- a/res/layout/drawer_footer_item.xml +++ b/res/layout/drawer_footer_item.xml @@ -17,30 +17,43 @@ --> <!-- Item in the drawer that launches the Help or Feedback activities. --> -<LinearLayout - xmlns:android="http://schemas.android.com/apk/res/android" - android:id="@+id/drawer_footer_view" - android:layout_height="wrap_content" +<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" - android:paddingLeft="@dimen/drawer_footer_item_padding" - android:paddingRight="@dimen/drawer_footer_item_padding" - android:minHeight="@dimen/drawer_footer_item_minimum_height" - android:gravity="center_vertical" - android:background="@drawable/nonfolder_item"> - - <ImageView - android:id="@+id/drawer_footer_image" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:duplicateParentState="true"/> + android:layout_height="wrap_content" + android:background="@color/footer_background_color"> - <TextView - android:id="@+id/drawer_footer_text" + <LinearLayout android:layout_height="wrap_content" - android:layout_width="wrap_content" - android:ellipsize="end" - android:textColor="@color/dark_gray_text_color" - android:textAllCaps="true" - android:textAppearance="?android:attr/textAppearanceMedium" - style="@style/DrawerFooterListItemStyle"/> -</LinearLayout> + android:layout_width="match_parent" + android:paddingLeft="@dimen/drawer_footer_item_padding" + android:paddingRight="@dimen/drawer_footer_item_padding" + android:minHeight="@dimen/drawer_footer_item_minimum_height" + android:gravity="center_vertical" + android:background="@drawable/nonfolder_item"> + + <ImageView + android:id="@+id/drawer_footer_image" + android:layout_width="wrap_content" + android:layout_height="wrap_content" /> + + <TextView + android:id="@+id/drawer_footer_text" + android:layout_height="wrap_content" + android:layout_width="wrap_content" + android:ellipsize="end" + android:textColor="@color/dark_gray_text_color" + android:textAllCaps="true" + android:textAppearance="?android:attr/textAppearanceSmall" + style="@style/DrawerFooterListItemStyle"/> + </LinearLayout> + + <!-- This top "border" is deliberately inset into the item to prevent it from interfering --> + <!-- visually with its floaty counterpart. This is also why it isn't just its own list item. --> + <View + android:id="@+id/top_border" + android:layout_width="match_parent" + android:layout_height="2dp" + android:background="@color/separator_color" + android:visibility="gone" /> + +</FrameLayout> diff --git a/res/layout/folder_list.xml b/res/layout/folder_list.xml index 59c294aeb..c5afea0f1 100644 --- a/res/layout/folder_list.xml +++ b/res/layout/folder_list.xml @@ -20,10 +20,44 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - <ListView + <com.android.mail.ui.ScrollNotifyingListView android:id="@android:id/list" android:layout_width="match_parent" android:layout_height="match_parent" - android:drawSelectorOnTop="false" - android:fadingEdge="none"/> + android:fadingEdge="none" + android:scrollbars="none" /> + + <LinearLayout + android:id="@+id/floaty_footer" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_gravity="bottom" + android:orientation="vertical"> + + <View + android:layout_width="match_parent" + android:layout_height="8dp" + android:background="@drawable/shadow_top" /> + + <View + android:layout_width="match_parent" + android:layout_height="1dp" + android:background="@color/separator_color" /> + + <LinearLayout + android:id="@+id/floaty_footer_items" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:background="@color/list_background_color"> + </LinearLayout> + + </LinearLayout> + + <com.android.mail.browse.ScrollIndicatorsView + android:id="@+id/scroll_indicators" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:scrollbars="vertical" /> + </com.android.mail.ui.FolderListLayout> diff --git a/res/values-h480dp/constants.xml b/res/values-h480dp/constants.xml new file mode 100644 index 000000000..07d306609 --- /dev/null +++ b/res/values-h480dp/constants.xml @@ -0,0 +1,20 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- + 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. +--> +<resources> + <bool name="show_drawer_floaty_footer">true</bool> +</resources> diff --git a/res/values-ldrtl/styles-ldrtl.xml b/res/values-ldrtl/styles-ldrtl.xml index 760e2b5fa..6d7162c20 100644 --- a/res/values-ldrtl/styles-ldrtl.xml +++ b/res/values-ldrtl/styles-ldrtl.xml @@ -91,7 +91,7 @@ </style> <style name="DrawerFooterListItemStyle"> - <item name="android:paddingStart">@dimen/drawer_footer_item_padding</item> + <item name="android:paddingStart">@dimen/drawer_footer_text_padding_start</item> </style> <style name="DismissSeparatorStyle"> diff --git a/res/values/colors.xml b/res/values/colors.xml index 875b16ec5..0ee3d0a6c 100644 --- a/res/values/colors.xml +++ b/res/values/colors.xml @@ -45,7 +45,9 @@ <!-- Folder List/Drawer colors --> <color name="account_item_selected_text_color">@color/mail_app_blue</color> <color name="folder_list_heading_text_color">@color/dark_gray_text_color</color> - <color name="list_background_color">#eeeeee</color> + <color name="list_background_color">@android:color/white</color> + <color name="footer_background_color">#eeeeee</color> + <color name="separator_color">#bebebe</color> <!-- Compose colors --> <color name="compose_label_text">#aaaaaa</color> diff --git a/res/values/constants.xml b/res/values/constants.xml index f52c3560f..1606aa1d9 100644 --- a/res/values/constants.xml +++ b/res/values/constants.xml @@ -61,6 +61,9 @@ <!-- Whether to show single or 2 pane search results --> <bool name="show_two_pane_search_results">false</bool> + <!-- this is off for shortish devices. values-h480dp enables this for anything tallish --> + <bool name="show_drawer_floaty_footer">false</bool> + <!-- Whether to show the archive item as disabled rather than hiding it entirely --> <bool name="show_disabled_archive_menu_item">false</bool> diff --git a/res/values/dimen.xml b/res/values/dimen.xml index 3e96c2d5f..c34b31fdf 100644 --- a/res/values/dimen.xml +++ b/res/values/dimen.xml @@ -91,8 +91,9 @@ <dimen name="account_item_minimum_height">48dip</dimen> <dimen name="account_item_swatch_height">8dip</dimen> <dimen name="account_item_folder_color_width">36dip</dimen> - <dimen name="drawer_footer_item_padding">9dp</dimen> - <dimen name="drawer_footer_item_minimum_height">48dp</dimen> + <dimen name="drawer_footer_item_padding">6dp</dimen> + <dimen name="drawer_footer_text_padding_start">8dp</dimen> + <dimen name="drawer_footer_item_minimum_height">40dp</dimen> <dimen name="widget_subject_font_size">13sp</dimen> <dimen name="widget_date_font_size">12sp</dimen> <dimen name="widget_margin_top">0dip</dimen> diff --git a/res/values/styles.xml b/res/values/styles.xml index a6565f9e7..7340a8bf5 100644 --- a/res/values/styles.xml +++ b/res/values/styles.xml @@ -578,7 +578,7 @@ </style> <style name="DrawerFooterListItemStyle"> - <item name="android:paddingLeft">@dimen/drawer_footer_item_padding</item> + <item name="android:paddingLeft">@dimen/drawer_footer_text_padding_start</item> </style> <style name="DismissSeparatorStyle"> diff --git a/src/com/android/mail/browse/ScrollIndicatorsView.java b/src/com/android/mail/browse/ScrollIndicatorsView.java index b5562c578..3a3055b13 100644 --- a/src/com/android/mail/browse/ScrollIndicatorsView.java +++ b/src/com/android/mail/browse/ScrollIndicatorsView.java @@ -1,4 +1,19 @@ -// Copyright 2011 Google Inc. All Rights Reserved. +/* + * Copyright (C) 2011 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.browse; diff --git a/src/com/android/mail/providers/DrawerClosedObserver.java b/src/com/android/mail/providers/DrawerClosedObserver.java index 55d76fb8f..1ab805924 100644 --- a/src/com/android/mail/providers/DrawerClosedObserver.java +++ b/src/com/android/mail/providers/DrawerClosedObserver.java @@ -18,14 +18,19 @@ package com.android.mail.providers; import android.database.DataSetObserver; +import android.support.v4.widget.DrawerLayout.DrawerListener; import com.android.mail.ui.AccountController; +import com.android.mail.ui.DrawerController; import com.android.mail.ui.RecentFolderController; /** * Observes when the drawer is closed for the purpose of computing after the drawer is, * potentially, off-screen. + * + * @deprecated TODO: switch this over to {@link DrawerController} and regular {@link DrawerListener} */ +@Deprecated public abstract class DrawerClosedObserver extends DataSetObserver { private AccountController mController; diff --git a/src/com/android/mail/ui/AbstractActivityController.java b/src/com/android/mail/ui/AbstractActivityController.java index 2b8cfefa9..83c3a13d3 100644 --- a/src/com/android/mail/ui/AbstractActivityController.java +++ b/src/com/android/mail/ui/AbstractActivityController.java @@ -41,6 +41,7 @@ import android.content.res.Resources; import android.database.Cursor; import android.database.DataSetObservable; import android.database.DataSetObserver; +import android.database.Observable; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; @@ -100,8 +101,8 @@ import com.android.mail.utils.ContentProviderTask; import com.android.mail.utils.DrawIdler; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; +import com.android.mail.utils.MailObservable; import com.android.mail.utils.NotificationActionUtils; -import com.android.mail.utils.Observable; import com.android.mail.utils.Utils; import com.android.mail.utils.VeiledAddressMatcher; import com.google.common.base.Objects; @@ -230,7 +231,7 @@ public abstract class AbstractActivityController implements ActivityController, private final Set<Uri> mCurrentAccountUris = Sets.newHashSet(); protected ConversationCursor mConversationListCursor; - private final DataSetObservable mConversationListObservable = new Observable("List"); + private final DataSetObservable mConversationListObservable = new MailObservable("List"); /** Runnable that checks the logging level to enable/disable the logging service. */ private Runnable mLogServiceChecker = null; @@ -255,15 +256,15 @@ public abstract class AbstractActivityController implements ActivityController, private RefreshTimerTask mConversationListRefreshTask; /** Listeners that are interested in changes to the current account. */ - private final DataSetObservable mAccountObservers = new Observable("Account"); + private final DataSetObservable mAccountObservers = new MailObservable("Account"); /** Listeners that are interested in changes to the recent folders. */ - private final DataSetObservable mRecentFolderObservers = new Observable("RecentFolder"); + private final DataSetObservable mRecentFolderObservers = new MailObservable("RecentFolder"); /** Listeners that are interested in changes to the list of all accounts. */ - private final DataSetObservable mAllAccountObservers = new Observable("AllAccounts"); + private final DataSetObservable mAllAccountObservers = new MailObservable("AllAccounts"); /** Listeners that are interested in changes to the current folder. */ - private final DataSetObservable mFolderObservable = new Observable("CurrentFolder"); + private final DataSetObservable mFolderObservable = new MailObservable("CurrentFolder"); /** Listeners that are interested in changes to the drawer state. */ - private final DataSetObservable mDrawerObservers = new Observable("Drawer"); + private final DataSetObservable mDrawerObservers = new MailObservable("Drawer"); /** * Selected conversations, if any. @@ -470,7 +471,7 @@ public abstract class AbstractActivityController implements ActivityController, protected ListView mListViewForAnimating; protected boolean mHasNewAccountOrFolder; private boolean mConversationListLoadFinishedIgnored; - protected MailDrawerListener mDrawerListener; + private final MailDrawerListener mDrawerListener = new MailDrawerListener(); private boolean mHideMenuItems; private final DrawIdler mDrawIdler = new DrawIdler(); @@ -1255,7 +1256,6 @@ public abstract class AbstractActivityController implements ActivityController, mDrawerToggle = new ActionBarDrawerToggle((Activity) mActivity, mDrawerContainer, R.drawable.ic_drawer, R.string.drawer_open, R.string.drawer_close); - mDrawerListener = new MailDrawerListener(); mDrawerContainer.setDrawerListener(mDrawerListener); mDrawerContainer.setDrawerShadow( mContext.getResources().getDrawable(R.drawable.drawer_shadow), Gravity.START); @@ -4144,7 +4144,13 @@ public abstract class AbstractActivityController implements ActivityController, mDetachedConvUri = null; } - private class MailDrawerListener implements DrawerLayout.DrawerListener { + @Override + public DrawerController getDrawerController() { + return mDrawerListener; + } + + private class MailDrawerListener extends Observable<DrawerLayout.DrawerListener> + implements DrawerLayout.DrawerListener, DrawerController { private int mDrawerState; private float mOldSlideOffset; @@ -4154,8 +4160,32 @@ public abstract class AbstractActivityController implements ActivityController, } @Override + public void registerDrawerListener(DrawerLayout.DrawerListener l) { + registerObserver(l); + } + + @Override + public void unregisterDrawerListener(DrawerLayout.DrawerListener l) { + unregisterObserver(l); + } + + @Override + public boolean isDrawerOpen() { + return isDrawerEnabled() && mDrawerContainer.isDrawerOpen(mDrawerPullout); + } + + @Override + public boolean isDrawerVisible() { + return isDrawerEnabled() && mDrawerContainer.isDrawerVisible(mDrawerPullout); + } + + @Override public void onDrawerOpened(View drawerView) { mDrawerToggle.onDrawerOpened(drawerView); + + for (DrawerLayout.DrawerListener l : mObservers) { + l.onDrawerOpened(drawerView); + } } @Override @@ -4169,6 +4199,10 @@ public abstract class AbstractActivityController implements ActivityController, final int mode = mViewMode.getMode(); final boolean isTopLevel = (mFolder == null) || (mFolder.parent == Uri.EMPTY); mDrawerToggle.setDrawerIndicatorEnabled(getShouldShowDrawerIndicator(mode, isTopLevel)); + + for (DrawerLayout.DrawerListener l : mObservers) { + l.onDrawerClosed(drawerView); + } } /** @@ -4220,6 +4254,10 @@ public abstract class AbstractActivityController implements ActivityController, // If we're sliding, we always want to show the burger mDrawerToggle.setDrawerIndicatorEnabled(true /* enable */); + + for (DrawerLayout.DrawerListener l : mObservers) { + l.onDrawerSlide(drawerView, slideOffset); + } } /** @@ -4232,6 +4270,11 @@ public abstract class AbstractActivityController implements ActivityController, LogUtils.d(LOG_TAG, "AAC onDrawerStateChanged %d", newState); mDrawerState = newState; mDrawerToggle.onDrawerStateChanged(mDrawerState); + + for (DrawerLayout.DrawerListener l : mObservers) { + l.onDrawerStateChanged(newState); + } + if (mViewMode.isSearchMode()) { return; } diff --git a/src/com/android/mail/ui/ActivityController.java b/src/com/android/mail/ui/ActivityController.java index b8ec206cf..93d6699d1 100644 --- a/src/com/android/mail/ui/ActivityController.java +++ b/src/com/android/mail/ui/ActivityController.java @@ -326,4 +326,6 @@ public interface ActivityController extends LayoutListener, * items that are not applicable while the drawer is open. */ public boolean shouldHideMenuItems(); + + DrawerController getDrawerController(); } diff --git a/src/com/android/mail/ui/ControllableActivity.java b/src/com/android/mail/ui/ControllableActivity.java index 72c113c4c..b85eec06e 100644 --- a/src/com/android/mail/ui/ControllableActivity.java +++ b/src/com/android/mail/ui/ControllableActivity.java @@ -96,6 +96,8 @@ public interface ControllableActivity extends HelpCallback, RestrictedActivity, UpOrBackController getUpOrBackController(); + DrawerController getDrawerController(); + void startDragMode(); void stopDragMode(); diff --git a/src/com/android/mail/ui/DrawerController.java b/src/com/android/mail/ui/DrawerController.java new file mode 100644 index 000000000..4ab0becf8 --- /dev/null +++ b/src/com/android/mail/ui/DrawerController.java @@ -0,0 +1,32 @@ +/* + * 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.support.v4.widget.DrawerLayout; + +/** + * Drawer-related functionality. + */ +public interface DrawerController { + + void registerDrawerListener(DrawerLayout.DrawerListener l); + void unregisterDrawerListener(DrawerLayout.DrawerListener l); + boolean isDrawerOpen(); + boolean isDrawerVisible(); + +} diff --git a/src/com/android/mail/ui/FolderListFragment.java b/src/com/android/mail/ui/FolderListFragment.java index b6bff1937..bf703edfc 100644 --- a/src/com/android/mail/ui/FolderListFragment.java +++ b/src/com/android/mail/ui/FolderListFragment.java @@ -17,15 +17,21 @@ package com.android.mail.ui; +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.app.ListFragment; import android.app.LoaderManager; import android.content.Loader; import android.net.Uri; import android.os.Bundle; +import android.support.v4.widget.DrawerLayout; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.animation.DecelerateInterpolator; +import android.view.animation.Interpolator; +import android.widget.AbsListView; import android.widget.ArrayAdapter; import android.widget.BaseAdapter; import android.widget.ImageView; @@ -37,6 +43,7 @@ import com.android.mail.R; import com.android.mail.adapter.DrawerItem; import com.android.mail.analytics.Analytics; import com.android.mail.browse.MergedAdapter; +import com.android.mail.browse.ScrollIndicatorsView; import com.android.mail.content.ObjectCursor; import com.android.mail.content.ObjectCursorLoader; import com.android.mail.providers.Account; @@ -97,6 +104,7 @@ public class FolderListFragment extends ListFragment implements private ControllableActivity mActivity; /** The underlying list view */ private ListView mListView; + private ViewGroup mFloatyFooter; /** URI that points to the list of folders for the current account. */ private Uri mFolderListUri; /** @@ -176,6 +184,15 @@ public class FolderListFragment extends ListFragment implements private FolderWatcher mFolderWatcher = null; private boolean mRegistered = false; + private final DrawerStateListener mDrawerListener = new DrawerStateListener(); + + private boolean mShowFooter; + + private boolean mFooterIsAnimating = false; + + private static final Interpolator INTERPOLATOR_SHOW_FLOATY = new DecelerateInterpolator(2.0f); + private static final Interpolator INTERPOLATOR_HIDE_FLOATY = new DecelerateInterpolator(); + /** * Constructor needs to be public to handle orientation changes and activity lifecycle events. */ @@ -256,7 +273,7 @@ public class FolderListFragment extends ListFragment implements // activity is creating ConversationListFragments. This activity must be of type // ControllableActivity. final Activity activity = getActivity(); - if (! (activity instanceof ControllableActivity)){ + if (!(activity instanceof ControllableActivity)) { LogUtils.wtf(LOG_TAG, "FolderListFragment expects only a ControllableActivity to" + "create it. Cannot proceed."); return; @@ -292,6 +309,7 @@ public class FolderListFragment extends ListFragment implements mAccountsAdapter = newAccountsAdapter(); mFooterAdapter = new FooterAdapter(); + updateFloatyFooter(); // Is the selected folder fresher than the one we have restored from a bundle? if (selectedFolder != null @@ -344,6 +362,8 @@ public class FolderListFragment extends ListFragment implements } }; mDrawerObserver.initialize(accountController); + + mActivity.getDrawerController().registerDrawerListener(mDrawerListener); } if (mActivity.isFinishing()) { @@ -384,7 +404,9 @@ public class FolderListFragment extends ListFragment implements setInstanceFromBundle(getArguments()); final View rootView = inflater.inflate(R.layout.folder_list, null); - mListView = (ListView) rootView.findViewById(android.R.id.list); + final ScrollNotifyingListView lv = (ScrollNotifyingListView) rootView.findViewById( + android.R.id.list); + mListView = lv; mListView.setEmptyView(null); mListView.setDivider(null); if (savedState != null && savedState.containsKey(BUNDLE_LIST_STATE)) { @@ -400,6 +422,49 @@ public class FolderListFragment extends ListFragment implements // No selected folder type required for hierarchical lists. } + mShowFooter = getResources().getBoolean(R.bool.show_help_and_feedback_in_drawer); + + final boolean floatyFooterEnabled = mShowFooter && getResources().getBoolean( + R.bool.show_drawer_floaty_footer); + final ViewGroup ff = (ViewGroup) rootView.findViewById(R.id.floaty_footer); + ff.setVisibility(floatyFooterEnabled ? View.VISIBLE : View.GONE); + if (floatyFooterEnabled) { + mFloatyFooter = ff; + } + + final ScrollIndicatorsView scrollbars = + (ScrollIndicatorsView) rootView.findViewById(R.id.scroll_indicators); + scrollbars.setSourceView(lv); + + mListView.setOnScrollListener(new AbsListView.OnScrollListener() { + + private int mLastState = AbsListView.OnScrollListener.SCROLL_STATE_IDLE; + + @Override + public void onScrollStateChanged(AbsListView view, int scrollState) { + mLastState = scrollState; + } + + @Override + public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, + int totalItemCount) { + // have the floaty footer react only after some non-zero scroll movement + if (mLastState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) { + // ignore onScrolls that are generated as the list data is updated + return; + } + + if (mListView.canScrollVertically(-1)) { + // typically we want scroll motion to hide the floaty footer + hideFloatyFooter(); + } else { + // except at the top, when we should show it + showFloatyFooter(false /* onlyWhenClosed */); + } + } + + }); + return rootView; } @@ -455,6 +520,10 @@ public class FolderListFragment extends ListFragment implements mDrawerObserver = null; } super.onDestroyView(); + + if (mActivity != null) { + mActivity.getDrawerController().unregisterDrawerListener(mDrawerListener); + } } @Override @@ -520,7 +589,7 @@ public class FolderListFragment extends ListFragment implements folder = (Folder) item; } else if (item instanceof FooterItem) { folder = null; - ((FooterItem) item).onFolderItemClicked(); + ((FooterItem) item).onClick(null /* unused */); } else { // Don't know how we got here. LogUtils.wtf(LOG_TAG, "viewFolderOrChangeAccount(): invalid item"); @@ -588,6 +657,9 @@ public class FolderListFragment extends ListFragment implements } else if (loader.getId() == ALL_FOLDER_LIST_LOADER_ID) { mFolderAdapter.setAllFolderListCursor(data); } + // new data means drawer list length may have changed, and the floaty footer visibility + // may need to be updated + showFloatyFooter(true /* onlyWhenClosed */); } } @@ -1122,6 +1194,9 @@ public class FolderListFragment extends ListFragment implements final FooterItem item = (FooterItem) getItem(position); + footerItemView.findViewById(R.id.top_border).setVisibility( + item.shouldShowTopBorder() ? View.VISIBLE : View.GONE); + // adjust the text of the footer item final TextView textView = (TextView) footerItemView. findViewById(R.id.drawer_footer_text); @@ -1139,23 +1214,30 @@ public class FolderListFragment extends ListFragment implements * is populated with URIs that navigate to appropriate destinations. */ private void update() { + // if the parent activity shows a drawer, these items should participate in that drawer + // (if it shows a *pane* they should *not* participate in that pane) + if (!mShowFooter) { + return; + } + mFooterItems.clear(); - final boolean showHelpAndFeedback = getResources() - .getBoolean(R.bool.show_help_and_feedback_in_drawer); + if (mCurrentAccount != null) { + mFooterItems.add(new SettingsItem()); + } - // if the parent activity shows a drawer, these items should participate in that drawer - // (if it shows a *pane* they should *not* participate in that pane) - if (showHelpAndFeedback) { - if (mCurrentAccount != null && !Utils.isEmpty(mCurrentAccount.helpIntentUri)) { - mFooterItems.add(new HelpFooterItem()); - } + if (mCurrentAccount != null && !Utils.isEmpty(mCurrentAccount.helpIntentUri)) { + mFooterItems.add(new HelpItem()); + } - // if a feedback Uri exists, show the Feedback drawer item - if (mCurrentAccount != null && - !Utils.isEmpty(mCurrentAccount.sendFeedbackIntentUri)) { - mFooterItems.add(new FeedbackFooterItem()); - } + // if a feedback Uri exists, show the Feedback drawer item + if (mCurrentAccount != null && + !Utils.isEmpty(mCurrentAccount.sendFeedbackIntentUri)) { + mFooterItems.add(new FeedbackItem()); + } + + if (!mFooterItems.isEmpty()) { + mFooterItems.get(0).setShowTopBorder(true); } notifyDataSetChanged(); @@ -1202,13 +1284,13 @@ public class FolderListFragment extends ListFragment implements * Sets the current account to the one provided here. * @param account the current account to set to. */ - private void setSelectedAccount(Account account){ + private void setSelectedAccount(Account account) { final boolean changed = (account != null) && (mCurrentAccount == null || !mCurrentAccount.uri.equals(account.uri)); mCurrentAccount = account; if (changed) { // Verify that the new account supports sending application feedback - mFooterAdapter.update(); + updateFooterItems(); // We no longer have proper folder objects. Let the new ones come in mFolderAdapter.setCursor(null); // If currentAccount is different from the one we set, restart the loader. Look at the @@ -1234,6 +1316,33 @@ public class FolderListFragment extends ListFragment implements } } + private void updateFooterItems() { + mFooterAdapter.update(); + updateFloatyFooter(); + } + + private void updateFloatyFooter() { + if (mFloatyFooter == null) { + return; + } + + // assuming this isn't often (the caller is debounced), just remake the floaty footer + // from scratch + final ViewGroup container = (ViewGroup) mFloatyFooter.findViewById( + R.id.floaty_footer_items); + container.removeAllViews(); + + for (int i = 0; i < mFooterAdapter.getCount(); i++) { + final View v = mFooterAdapter.getView(i, null /* convertView */, mFloatyFooter); + // the floaty version has its own top border, so remove the list version's border + if (i == 0) { + v.findViewById(R.id.top_border).setVisibility(View.GONE); + } + v.setOnClickListener((FooterItem) mFooterAdapter.getItem(i)); + container.addView(v); + } + } + /** * Checks if the specified {@link Folder} is a type that we want to exclude from displaying. */ @@ -1258,15 +1367,72 @@ public class FolderListFragment extends ListFragment implements return mAccountController.getFolderListViewChoiceMode(); } + private void showFloatyFooter(boolean onlyWhenClosed) { + // don't ever show if the footer is disabled (in onCreateView) + if (mFloatyFooter == null) { + return; + } + + // don't show when onLoadFinished is the reason to show it, and the drawer is open + // (minimize user-visible changes; that case is basically only relevant when closed) + final boolean drawerIsOpen = mActivity.getDrawerController().isDrawerOpen(); + if (onlyWhenClosed && drawerIsOpen) { + return; + } + + // show the footer, unless if we're already at the very bottom + final int vis = getListView().canScrollVertically(+1) ? View.VISIBLE : View.GONE; + + mFloatyFooter.animate().cancel(); + if (drawerIsOpen && vis == View.VISIBLE) { + mFloatyFooter.animate() + .translationY(0f) + .setDuration(150) + .setInterpolator(INTERPOLATOR_SHOW_FLOATY) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationStart(Animator animation) { + mFloatyFooter.setVisibility(vis); + } + }); + } else { + mFloatyFooter.setTranslationY(0f); + mFloatyFooter.setVisibility(vis); + } + + } + + private void hideFloatyFooter() { + if (mFloatyFooter == null || mFloatyFooter.getVisibility() == View.GONE + || mFooterIsAnimating) { + return; + } + mFooterIsAnimating = true; + mFloatyFooter.animate().cancel(); + mFloatyFooter.animate() + .translationY(mFloatyFooter.getHeight()) + .setDuration(200) + .setInterpolator(INTERPOLATOR_HIDE_FLOATY) + .setListener(new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animation) { + mFooterIsAnimating = false; + mFloatyFooter.setVisibility(View.GONE); + } + }); + } + /** * The base class of all footer items. Subclasses must fill in the logic of - * {@link #onFolderItemClicked()} which contains the behavior when the item is selected. + * {@link #doFooterAction()} which contains the behavior when the item is selected. */ - private abstract class FooterItem { + private abstract class FooterItem implements View.OnClickListener { private final int mImageResourceID; private final int mTextResourceID; + private boolean mShowTopBorder; + private FooterItem(final int imageResourceID, final int textResourceID) { mImageResourceID = imageResourceID; mTextResourceID = textResourceID; @@ -1281,34 +1447,94 @@ public class FolderListFragment extends ListFragment implements } /** - * Executes the behavior associated with this footer item. + * Executes the behavior associated with this footer item.<br> + * <br> + * WARNING: you probably don't want to call this directly; use + * {@link #onClick(View)} instead. This method actually performs the action, and its + * execution is deferred from when the 'click' happens so we can smoothly close the drawer + * beforehand. */ - abstract void onFolderItemClicked(); + abstract void doFooterAction(); + + @Override + public final void onClick(View v) { + // close the drawer and defer handling the click until onDrawerClosed + mAccountController.closeDrawer(false /* hasNewFolderOrAccount */, + null /* nextAccount */, null /* nextFolder */); + mDrawerListener.setPendingFooterClick(this); + } + + public boolean shouldShowTopBorder() { + return mShowTopBorder; + } + + public void setShowTopBorder(boolean show) { + mShowTopBorder = show; + } + } - /** - * The 'Help" footer item. - */ - private class HelpFooterItem extends FooterItem { - protected HelpFooterItem() { - super(R.drawable.ic_drawer_help, R.string.help_and_info); + private class HelpItem extends FooterItem { + protected HelpItem() { + super(R.drawable.ic_menu_help, R.string.help_and_info); } - void onFolderItemClicked() { + @Override + void doFooterAction() { Utils.showHelp(getActivity(), mCurrentAccount, mActivity.getHelpContext()); } } + private class FeedbackItem extends FooterItem { + protected FeedbackItem() { + super(R.drawable.ic_menu_feedback, R.string.feedback); + } + + @Override + void doFooterAction() { + Utils.sendFeedback(getActivity(), mCurrentAccount, false); + } + } + + private class SettingsItem extends FooterItem { + protected SettingsItem() { + super(R.drawable.ic_menu_settings, R.string.menu_settings); + } + + @Override + void doFooterAction() { + Utils.showSettings(mActivity.getActivityContext(), mCurrentAccount); + } + } + /** - * The 'Send Feedback" footer item. + * Drawer listener for footer functionality to react to drawer state. */ - private class FeedbackFooterItem extends FooterItem { - protected FeedbackFooterItem() { - super(R.drawable.ic_drawer_feedback, R.string.feedback); + private class DrawerStateListener implements DrawerLayout.DrawerListener { + + private FooterItem mPendingFooterClick; + + public void setPendingFooterClick(FooterItem itemClicked) { + mPendingFooterClick = itemClicked; } - void onFolderItemClicked() { - Utils.sendFeedback(getActivity(), mCurrentAccount, false); + @Override + public void onDrawerSlide(View drawerView, float slideOffset) {} + + @Override + public void onDrawerOpened(View drawerView) {} + + @Override + public void onDrawerClosed(View drawerView) { + showFloatyFooter(false /* onlyWhenClosed */); + if (mPendingFooterClick != null) { + mPendingFooterClick.doFooterAction(); + mPendingFooterClick = null; + } } + + @Override + public void onDrawerStateChanged(int newState) {} + } } diff --git a/src/com/android/mail/ui/FolderSelectionActivity.java b/src/com/android/mail/ui/FolderSelectionActivity.java index 3573ce6e9..06bf6ab71 100644 --- a/src/com/android/mail/ui/FolderSelectionActivity.java +++ b/src/com/android/mail/ui/FolderSelectionActivity.java @@ -39,7 +39,7 @@ import com.android.mail.providers.Folder; import com.android.mail.providers.FolderWatcher; import com.android.mail.utils.LogTag; import com.android.mail.utils.LogUtils; -import com.android.mail.utils.Observable; +import com.android.mail.utils.MailObservable; import com.android.mail.utils.Utils; import com.android.mail.utils.VeiledAddressMatcher; import com.android.mail.widget.WidgetProvider; @@ -67,7 +67,7 @@ public class FolderSelectionActivity extends Activity implements OnClickListener private int mAppWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID; private int mMode = -1; /** Empty placeholder for communicating to the consumer of the drawer observer. */ - private final DataSetObservable mDrawerObservers = new Observable("Drawer"); + private final DataSetObservable mDrawerObservers = new MailObservable("Drawer"); private final AccountController mAccountController = new AccountController() { @Override @@ -451,6 +451,12 @@ public class FolderSelectionActivity extends Activity implements OnClickListener } @Override + public DrawerController getDrawerController() { + // Unsupported + return null; + } + + @Override public boolean isAccessibilityEnabled() { // Unsupported return true; diff --git a/src/com/android/mail/ui/MailActionBarView.java b/src/com/android/mail/ui/MailActionBarView.java index e7458a0b3..2bb9bc8fd 100644 --- a/src/com/android/mail/ui/MailActionBarView.java +++ b/src/com/android/mail/ui/MailActionBarView.java @@ -89,6 +89,7 @@ public class MailActionBarView extends LinearLayout implements ViewMode.ModeChan private Folder mFolder; private SearchView mSearchWidget; + private MenuItem mSettingsItem; private MenuItem mHelpItem; private MenuItem mSendFeedbackItem; private MenuItem mFolderSettingsItem; @@ -241,6 +242,7 @@ public class MailActionBarView extends LinearLayout implements ViewMode.ModeChan mSearchWidget.setIconifiedByDefault(true); } } + mSettingsItem = menu.findItem(R.id.settings); mHelpItem = menu.findItem(R.id.help_info_menu_item); mSendFeedbackItem = menu.findItem(R.id.feedback_menu_item); mFolderSettingsItem = menu.findItem(R.id.folder_options); @@ -374,6 +376,9 @@ public class MailActionBarView extends LinearLayout implements ViewMode.ModeChan final boolean showHelpandFeedback = !getResources() .getBoolean(R.bool.show_help_and_feedback_in_drawer); + if (mSettingsItem != null) { + mSettingsItem.setVisible(showHelpandFeedback); + } if (mHelpItem != null) { mHelpItem.setVisible(showHelpandFeedback && mAccount != null diff --git a/src/com/android/mail/ui/MailActivity.java b/src/com/android/mail/ui/MailActivity.java index 6ff05af4d..802511f52 100644 --- a/src/com/android/mail/ui/MailActivity.java +++ b/src/com/android/mail/ui/MailActivity.java @@ -385,6 +385,11 @@ public class MailActivity extends AbstractMailActivity implements ControllableAc } @Override + public DrawerController getDrawerController() { + return mController.getDrawerController(); + } + + @Override public void onFooterViewErrorActionClick(Folder folder, int errorStatus) { mController.onFooterViewErrorActionClick(folder, errorStatus); } diff --git a/src/com/android/mail/ui/ScrollNotifyingListView.java b/src/com/android/mail/ui/ScrollNotifyingListView.java new file mode 100644 index 000000000..d4e65d18e --- /dev/null +++ b/src/com/android/mail/ui/ScrollNotifyingListView.java @@ -0,0 +1,100 @@ +/* + * 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.database.Observable; +import android.util.AttributeSet; +import android.widget.ListView; + +import com.android.mail.browse.ScrollNotifier; + +/** + * Ordinary list view with extra boilerplate to notify on scrollbar-related events (unrelated to + * {@link android.widget.AbsListView.OnScrollListener}!) + */ +public class ScrollNotifyingListView extends ListView implements ScrollNotifier { + + private final ScrollObservable mObservable = new ScrollObservable(); + + public ScrollNotifyingListView(Context c) { + this(c, null); + } + + public ScrollNotifyingListView(Context c, AttributeSet attrs) { + super(c, attrs); + } + + @Override + public void addScrollListener(ScrollListener l) { + mObservable.registerObserver(l); + } + + @Override + public void removeScrollListener(ScrollListener l) { + mObservable.unregisterObserver(l); + } + + @Override + protected void onScrollChanged(int l, int t, int oldl, int oldt) { + super.onScrollChanged(l, t, oldl, oldt); + mObservable.onScrollChanged(l, t, oldl, oldt); + } + + @Override + public int computeVerticalScrollRange() { + return super.computeVerticalScrollRange(); + } + + @Override + public int computeVerticalScrollOffset() { + return super.computeVerticalScrollOffset(); + } + + @Override + public int computeVerticalScrollExtent() { + return super.computeVerticalScrollExtent(); + } + + @Override + public int computeHorizontalScrollRange() { + return super.computeHorizontalScrollRange(); + } + + @Override + public int computeHorizontalScrollOffset() { + return super.computeHorizontalScrollOffset(); + } + + @Override + public int computeHorizontalScrollExtent() { + return super.computeHorizontalScrollExtent(); + } + + private static class ScrollObservable extends Observable<ScrollListener> { + + @SuppressWarnings("unused") + public void onScrollChanged(int l, int t, int oldl, int oldt) { + for (ScrollListener sl : mObservers) { + sl.onNotifierScroll(t); + } + } + + } + +} diff --git a/src/com/android/mail/utils/Observable.java b/src/com/android/mail/utils/MailObservable.java index 81e8d8321..bd67c0a39 100644 --- a/src/com/android/mail/utils/Observable.java +++ b/src/com/android/mail/utils/MailObservable.java @@ -23,11 +23,11 @@ import android.database.DataSetObserver; * A Utility class to register observers and return logging and counts for the number of registered * observers. */ -public class Observable extends DataSetObservable { +public class MailObservable extends DataSetObservable { protected static final String LOG_TAG = LogTag.getLogTag(); private final String mName; - public Observable(String name) { + public MailObservable(String name) { mName = name; } |