blob: b7d9286c730278b3ae6654e9862e4d62f82d8c72 [file] [log] [blame]
/**
* 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.DataSetObserver;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import com.android.mail.R;
import com.android.mail.content.ObjectCursor;
import com.android.mail.providers.Folder;
import com.android.mail.utils.LogUtils;
import java.util.ArrayDeque;
import java.util.Queue;
/**
* A smaller version of the account- and folder-switching drawer view for tablet UIs.
*/
public class MiniDrawerView extends LinearLayout {
private FolderListFragment mController;
private View mSpacer;
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);
mInflater = LayoutInflater.from(context);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mSpacer = findViewById(R.id.spacer);
}
@Override
public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
// This ViewGroup is focusable purely so it can act as a stable target for other views to
// designate as their left/right focus ID. When focus comes to this view, the XML
// declaration of descendantFocusability=FOCUS_AFTER_DESCENDANTS means it will always try
// to focus one of its children before resorting to this (great! we basically never want
// this container to gain focus).
//
// But the usual focus search towards the LEFT (in LTR) actually starts at the bottom,
// which is weird. So override all focus requests that land on this parent to use the
// FORWARD direction so the top-most item gets first focus. This will not affect focus
// traversal within this ViewGroup as the descendantFocusability prevents the parent from
// gaining focus.
return super.requestFocus(FOCUS_DOWN, previouslyFocusedRect);
}
public void setController(FolderListFragment flf) {
mController = flf;
final ListAdapter adapter = mController.getMiniDrawerAccountsAdapter();
adapter.registerDataSetObserver(new Observer());
}
private class Observer extends DataSetObserver {
@Override
public void onChanged() {
refresh();
}
}
public void refresh() {
if (mController == null || !mController.isAdded()) {
return;
}
final ListAdapter adapter =
mController.getMiniDrawerAccountsAdapter();
if (adapter.getCount() > 0) {
final View oldCurrentAccountView = getChildAt(0);
if (oldCurrentAccountView != null) {
removeView(oldCurrentAccountView);
}
final View newCurrentAccountView = adapter.getView(0, oldCurrentAccountView, this);
newCurrentAccountView.setClickable(false);
newCurrentAccountView.setFocusable(false);
addView(newCurrentAccountView, 0);
}
final int removePos = indexOfChild(mSpacer) + 1;
final int recycleCount = getChildCount() - removePos;
final Queue<View> recycleViews = new ArrayDeque<>(recycleCount);
for (int recycleIndex = 0; recycleIndex < recycleCount; recycleIndex++) {
final View recycleView = getChildAt(removePos);
recycleViews.add(recycleView);
removeView(recycleView);
}
final int adapterCount = Math.min(adapter.getCount(), NUM_RECENT_ACCOUNTS + 1);
for (int accountIndex = 1; accountIndex < adapterCount; accountIndex++) {
final View recycleView = recycleViews.poll();
final View accountView = adapter.getView(accountIndex, recycleView, this);
addView(accountView);
}
View child;
// reset the inbox views for this account
while ((child=getChildAt(1)) != mSpacer) {
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 View view = mInflater.inflate(
R.layout.mini_drawer_folder_item, this, false /* attachToRoot */);
final ImageView iv = (ImageView) view.findViewById(R.id.image_view);
iv.setTag(new FolderItem(f, iv));
iv.setContentDescription(f.name);
view.setActivated(mController.isSelectedFolder(f));
addView(view, 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");
}
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// We want to make sure that all children get measured. These will be re-hidden in onLayout
// according to space constraints.
// This means we can't set views to Gone elsewhere, which is kind of unfortunate.
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
child.setVisibility(View.VISIBLE);
}
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
}
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
if (getChildCount() == 0) {
return;
}
final int availableHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop();
int childHeight = 0;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
if (child.equals(mSpacer) || child.getVisibility() == View.GONE) {
continue;
}
final LayoutParams params = (LayoutParams) child.getLayoutParams();
childHeight += params.topMargin + params.bottomMargin + child.getMeasuredHeight();
}
if (childHeight <= availableHeight) {
// Nothing to do here
super.onLayout(changed, l, t, r, b);
return;
}
// Check again
if (childHeight <= availableHeight) {
// Fit the spacer to the remaining height
measureSpacer(availableHeight - childHeight);
super.onLayout(changed, l, t, r, b);
return;
}
// Sanity check
if (getChildAt(getChildCount() - 1).equals(mSpacer)) {
LogUtils.v(LogUtils.TAG, "The ellipsis was the last item in the minidrawer and " +
"hiding it didn't help fit all the views");
return;
}
final View childToHide = getChildAt(indexOfChild(mSpacer) + 1);
childToHide.setVisibility(View.GONE);
final LayoutParams childToHideParams = (LayoutParams) childToHide.getLayoutParams();
childHeight -= childToHideParams.topMargin + childToHideParams.bottomMargin +
childToHide.getMeasuredHeight();
// Check again
if (childHeight <= availableHeight) {
// Fit the spacer to the remaining height
measureSpacer(availableHeight - childHeight);
super.onLayout(changed, l, t, r, b);
return;
}
LogUtils.v(LogUtils.TAG, "Hid two children in the minidrawer and still couldn't fit " +
"all the views");
}
private void measureSpacer(int height) {
final LayoutParams spacerParams = (LayoutParams) mSpacer.getLayoutParams();
final int spacerHeight = height -
spacerParams.bottomMargin - spacerParams.topMargin;
final int spacerWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight();
mSpacer.measure(MeasureSpec.makeMeasureSpec(spacerWidth, MeasureSpec.AT_MOST),
MeasureSpec.makeMeasureSpec(spacerHeight, MeasureSpec.EXACTLY));
}
}