Home | History | Annotate | Download | only in ui
      1 /**
      2  * Copyright (C) 2014 Google Inc.
      3  * Licensed to The Android Open Source Project.
      4  *
      5  * Licensed under the Apache License, Version 2.0 (the "License");
      6  * you may not use this file except in compliance with the License.
      7  * You may obtain a copy of the License at
      8  *
      9  *      http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  * Unless required by applicable law or agreed to in writing, software
     12  * distributed under the License is distributed on an "AS IS" BASIS,
     13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  * See the License for the specific language governing permissions and
     15  * limitations under the License.
     16  */
     17 package com.android.mail.ui;
     18 
     19 import android.content.Context;
     20 import android.database.DataSetObserver;
     21 import android.graphics.Rect;
     22 import android.util.AttributeSet;
     23 import android.view.LayoutInflater;
     24 import android.view.View;
     25 import android.widget.ImageView;
     26 import android.widget.LinearLayout;
     27 import android.widget.ListAdapter;
     28 
     29 import com.android.mail.R;
     30 import com.android.mail.content.ObjectCursor;
     31 import com.android.mail.providers.Folder;
     32 import com.android.mail.utils.LogUtils;
     33 
     34 import java.util.ArrayDeque;
     35 import java.util.Queue;
     36 
     37 /**
     38  * A smaller version of the account- and folder-switching drawer view for tablet UIs.
     39  */
     40 public class MiniDrawerView extends LinearLayout {
     41 
     42     private FolderListFragment mController;
     43 
     44     private View mSpacer;
     45 
     46     private final LayoutInflater mInflater;
     47 
     48     private static final int NUM_RECENT_ACCOUNTS = 2;
     49 
     50     public MiniDrawerView(Context context) {
     51         this(context, null);
     52     }
     53 
     54     public MiniDrawerView(Context context, AttributeSet attrs) {
     55         super(context, attrs);
     56 
     57         mInflater = LayoutInflater.from(context);
     58     }
     59 
     60     @Override
     61     protected void onFinishInflate() {
     62         super.onFinishInflate();
     63 
     64         mSpacer = findViewById(R.id.spacer);
     65     }
     66 
     67     @Override
     68     public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
     69         // This ViewGroup is focusable purely so it can act as a stable target for other views to
     70         // designate as their left/right focus ID. When focus comes to this view, the XML
     71         // declaration of descendantFocusability=FOCUS_AFTER_DESCENDANTS means it will always try
     72         // to focus one of its children before resorting to this (great! we basically never want
     73         // this container to gain focus).
     74         //
     75         // But the usual focus search towards the LEFT (in LTR) actually starts at the bottom,
     76         // which is weird. So override all focus requests that land on this parent to use the
     77         // FORWARD direction so the top-most item gets first focus. This will not affect focus
     78         // traversal within this ViewGroup as the descendantFocusability prevents the parent from
     79         // gaining focus.
     80         return super.requestFocus(FOCUS_DOWN, previouslyFocusedRect);
     81     }
     82 
     83     public void setController(FolderListFragment flf) {
     84         mController = flf;
     85         final ListAdapter adapter = mController.getMiniDrawerAccountsAdapter();
     86         adapter.registerDataSetObserver(new Observer());
     87     }
     88 
     89     private class Observer extends DataSetObserver {
     90 
     91         @Override
     92         public void onChanged() {
     93             refresh();
     94         }
     95     }
     96 
     97     public void refresh() {
     98         if (mController == null || !mController.isAdded()) {
     99             return;
    100         }
    101 
    102         final ListAdapter adapter =
    103                 mController.getMiniDrawerAccountsAdapter();
    104 
    105         if (adapter.getCount() > 0) {
    106             final View oldCurrentAccountView = getChildAt(0);
    107             if (oldCurrentAccountView != null) {
    108                 removeView(oldCurrentAccountView);
    109             }
    110             final View newCurrentAccountView = adapter.getView(0, oldCurrentAccountView, this);
    111             newCurrentAccountView.setClickable(false);
    112             newCurrentAccountView.setFocusable(false);
    113             addView(newCurrentAccountView, 0);
    114         }
    115 
    116         final int removePos = indexOfChild(mSpacer) + 1;
    117         final int recycleCount = getChildCount() - removePos;
    118         final Queue<View> recycleViews = new ArrayDeque<>(recycleCount);
    119         for (int recycleIndex = 0; recycleIndex < recycleCount; recycleIndex++) {
    120             final View recycleView = getChildAt(removePos);
    121             recycleViews.add(recycleView);
    122             removeView(recycleView);
    123         }
    124 
    125         final int adapterCount = Math.min(adapter.getCount(), NUM_RECENT_ACCOUNTS + 1);
    126         for (int accountIndex = 1; accountIndex < adapterCount; accountIndex++) {
    127             final View recycleView = recycleViews.poll();
    128             final View accountView = adapter.getView(accountIndex, recycleView, this);
    129             addView(accountView);
    130         }
    131 
    132         View child;
    133         // reset the inbox views for this account
    134         while ((child=getChildAt(1)) != mSpacer) {
    135             removeView(child);
    136         }
    137 
    138         final ObjectCursor<Folder> folderCursor = mController.getFoldersCursor();
    139         if (folderCursor != null && !folderCursor.isClosed()) {
    140             int pos = -1;
    141             int numInboxes = 0;
    142             while (folderCursor.moveToPosition(++pos)) {
    143                 final Folder f = folderCursor.getModel();
    144                 if (f.isInbox()) {
    145                     final View view = mInflater.inflate(
    146                             R.layout.mini_drawer_folder_item, this, false /* attachToRoot */);
    147                     final ImageView iv = (ImageView) view.findViewById(R.id.image_view);
    148                     iv.setTag(new FolderItem(f, iv));
    149                     iv.setContentDescription(f.name);
    150                     view.setActivated(mController.isSelectedFolder(f));
    151                     addView(view, 1 + numInboxes);
    152                     numInboxes++;
    153                 }
    154             }
    155         }
    156     }
    157 
    158     private class FolderItem implements View.OnClickListener {
    159         public final Folder folder;
    160         public final ImageView view;
    161 
    162         public FolderItem(Folder f, ImageView iv) {
    163             folder = f;
    164             view = iv;
    165             Folder.setIcon(folder, view);
    166             view.setOnClickListener(this);
    167         }
    168 
    169         @Override
    170         public void onClick(View v) {
    171             mController.onFolderSelected(folder, "mini-drawer");
    172         }
    173     }
    174 
    175     @Override
    176     protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    177         // We want to make sure that all children get measured. These will be re-hidden in onLayout
    178         // according to space constraints.
    179         // This means we can't set views to Gone elsewhere, which is kind of unfortunate.
    180         final int childCount = getChildCount();
    181         for (int i = 0; i < childCount; i++) {
    182             final View child = getChildAt(i);
    183             child.setVisibility(View.VISIBLE);
    184         }
    185         super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    186     }
    187 
    188     @Override
    189     protected void onLayout(boolean changed, int l, int t, int r, int b) {
    190         if (getChildCount() == 0) {
    191             return;
    192         }
    193         final int availableHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();
    194 
    195         int childHeight = 0;
    196         final int childCount = getChildCount();
    197         for (int i = 0; i < childCount; i++) {
    198             final View child = getChildAt(i);
    199             if (child.equals(mSpacer) || child.getVisibility() == View.GONE) {
    200                 continue;
    201             }
    202             final LayoutParams params = (LayoutParams) child.getLayoutParams();
    203             childHeight += params.topMargin + params.bottomMargin + child.getMeasuredHeight();
    204         }
    205 
    206         if (childHeight <= availableHeight) {
    207             // Nothing to do here
    208             super.onLayout(changed, l, t, r, b);
    209             return;
    210         }
    211 
    212         // Check again
    213         if (childHeight <= availableHeight) {
    214             // Fit the spacer to the remaining height
    215             measureSpacer(availableHeight - childHeight);
    216             super.onLayout(changed, l, t, r, b);
    217             return;
    218         }
    219 
    220         // Sanity check
    221         if (getChildAt(getChildCount() - 1).equals(mSpacer)) {
    222             LogUtils.v(LogUtils.TAG, "The ellipsis was the last item in the minidrawer and " +
    223                     "hiding it didn't help fit all the views");
    224             return;
    225         }
    226 
    227         final View childToHide = getChildAt(indexOfChild(mSpacer) + 1);
    228         childToHide.setVisibility(View.GONE);
    229 
    230         final LayoutParams childToHideParams = (LayoutParams) childToHide.getLayoutParams();
    231         childHeight -= childToHideParams.topMargin + childToHideParams.bottomMargin +
    232                 childToHide.getMeasuredHeight();
    233 
    234         // Check again
    235         if (childHeight <= availableHeight) {
    236             // Fit the spacer to the remaining height
    237             measureSpacer(availableHeight - childHeight);
    238             super.onLayout(changed, l, t, r, b);
    239             return;
    240         }
    241 
    242         LogUtils.v(LogUtils.TAG, "Hid two children in the minidrawer and still couldn't fit " +
    243                 "all the views");
    244     }
    245 
    246     private void measureSpacer(int height) {
    247         final LayoutParams spacerParams = (LayoutParams) mSpacer.getLayoutParams();
    248         final int spacerHeight = height -
    249                 spacerParams.bottomMargin - spacerParams.topMargin;
    250         final int spacerWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
    251         mSpacer.measure(MeasureSpec.makeMeasureSpec(spacerWidth, MeasureSpec.AT_MOST),
    252                 MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY));
    253 
    254     }
    255 }
    256