1/**
2 * Copyright (c) 2012, Google Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.mail.ui;
17
18import android.content.Context;
19import android.graphics.Color;
20import android.graphics.drawable.ShapeDrawable;
21import android.graphics.drawable.shapes.RoundRectShape;
22import android.graphics.drawable.shapes.Shape;
23import android.util.AttributeSet;
24import android.view.DragEvent;
25import android.view.View;
26import android.widget.ImageView;
27import android.widget.RelativeLayout;
28import android.widget.TextView;
29
30import com.android.mail.R;
31import com.android.mail.providers.Folder;
32import com.android.mail.utils.LogTag;
33import com.android.mail.utils.LogUtils;
34import com.android.mail.utils.Utils;
35
36/**
37 * The view for each folder in the folder list.
38 */
39public class FolderItemView extends RelativeLayout {
40    private final String LOG_TAG = LogTag.getLogTag();
41
42    private static final int[] STATE_DRAG_MODE = {R.attr.state_drag_mode};
43
44    private static float[] sUnseenCornerRadii;
45
46    private Folder mFolder;
47    private TextView mFolderTextView;
48    private TextView mUnreadCountTextView;
49    private TextView mUnseenCountTextView;
50    private DropHandler mDropHandler;
51    private ImageView mFolderParentIcon;
52
53    private boolean mIsDragMode;
54
55    /**
56     * A delegate for a handler to handle a drop of an item.
57     */
58    public interface DropHandler {
59        /**
60         * Return whether or not the drag event is supported by the drop handler. The
61         *     {@code FolderItemView} will present appropriate visual affordances if the drag is
62         *     supported.
63         */
64        boolean supportsDrag(DragEvent event, Folder folder);
65
66        /**
67         * Handles a drop event, applying the appropriate logic.
68         */
69        void handleDrop(DragEvent event, Folder folder);
70    }
71
72    public FolderItemView(Context context) {
73        super(context);
74
75        loadResources(context);
76    }
77
78    public FolderItemView(Context context, AttributeSet attrs) {
79        super(context, attrs);
80
81        loadResources(context);
82    }
83
84    public FolderItemView(Context context, AttributeSet attrs, int defStyle) {
85        super(context, attrs, defStyle);
86
87        loadResources(context);
88        mIsDragMode = false;
89    }
90
91    private void loadResources(Context context) {
92        if (sUnseenCornerRadii == null) {
93            final float cornerRadius =
94                    context.getResources().getDimension(R.dimen.folder_rounded_corner_radius);
95            sUnseenCornerRadii = new float[] {
96                    cornerRadius, cornerRadius, // top left
97                    cornerRadius, cornerRadius, // top right
98                    cornerRadius, cornerRadius, // bottom right
99                    cornerRadius, cornerRadius  // bottom left
100            };
101        }
102    }
103
104    @Override
105    protected void onFinishInflate() {
106        super.onFinishInflate();
107
108        mFolderTextView = (TextView)findViewById(R.id.name);
109        mUnreadCountTextView = (TextView)findViewById(R.id.unread);
110        mUnseenCountTextView = (TextView)findViewById(R.id.unseen);
111        mFolderParentIcon = (ImageView) findViewById(R.id.folder_parent_icon);
112    }
113
114    /**
115     * Returns true if the two folders lead to identical {@link FolderItemView} objects.
116     * @param a
117     * @param b
118     * @return true if the two folders would still lead to the same {@link FolderItemView}.
119     */
120    public static boolean areSameViews(final Folder a, final Folder b) {
121        if (a == null) {
122            return b == null;
123        }
124        if (b == null) {
125            // a is not null because it would have returned above.
126            return false;
127        }
128        return (a == b || (a.folderUri.equals(b.folderUri)
129                && a.name.equals(b.name)
130                && a.hasChildren == b.hasChildren
131                && a.unseenCount == b.unseenCount
132                && a.unreadCount == b.unreadCount));
133    }
134
135    public void bind(final Folder folder, final DropHandler dropHandler) {
136        mFolder = folder;
137        mDropHandler = dropHandler;
138
139        mFolderTextView.setText(folder.name);
140
141        mFolderParentIcon.setVisibility(mFolder.hasChildren ? View.VISIBLE : View.GONE);
142        if (mFolder.isInbox() && mFolder.unseenCount > 0) {
143            mUnreadCountTextView.setVisibility(View.GONE);
144            setUnseenCount(mFolder.getBackgroundColor(Color.BLACK), mFolder.unseenCount);
145        } else {
146            mUnseenCountTextView.setVisibility(View.GONE);
147            setUnreadCount(Utils.getFolderUnreadDisplayCount(mFolder));
148        }
149    }
150
151    /**
152     * Sets the icon, if any.
153     */
154    public void setIcon(final Folder folder) {
155        final ImageView folderIconView = (ImageView) findViewById(R.id.folder_icon);
156        Folder.setIcon(folder, folderIconView);
157    }
158
159    /**
160     * Sets the unread count, taking care to hide/show the textview if the count is zero/non-zero.
161     */
162    private void setUnreadCount(int count) {
163        mUnreadCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
164        if (count > 0) {
165            mUnreadCountTextView.setText(Utils.getUnreadCountString(getContext(), count));
166        }
167    }
168
169    /**
170     * Sets the unseen count, taking care to hide/show the textview if the count is zero/non-zero.
171     */
172    private void setUnseenCount(final int color, final int count) {
173        mUnseenCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
174        if (count > 0) {
175            final Shape shape = new RoundRectShape(sUnseenCornerRadii, null, null);
176            final ShapeDrawable drawable = new ShapeDrawable(shape);
177            drawable.getPaint().setColor(color);
178            mUnseenCountTextView.setBackgroundDrawable(drawable);
179            mUnseenCountTextView.setText(Utils.getUnseenCountString(getContext(), count));
180        }
181    }
182
183    /**
184     * Used if we detect a problem with the unread count and want to force an override.
185     * @param count
186     */
187    public final void overrideUnreadCount(int count) {
188        LogUtils.e(LOG_TAG, "FLF->FolderItem.getFolderView: unread count mismatch found (%s vs %d)",
189                mUnreadCountTextView.getText(), count);
190        setUnreadCount(count);
191    }
192
193    private boolean isDroppableTarget(DragEvent event) {
194        return (mDropHandler != null && mDropHandler.supportsDrag(event, mFolder));
195    }
196
197    /**
198     * Handles the drag event.
199     *
200     * @param event the drag event to be handled
201     */
202    @Override
203    public boolean onDragEvent(DragEvent event) {
204        switch (event.getAction()) {
205            case DragEvent.ACTION_DRAG_STARTED:
206                // Set drag mode state to true now that we have entered drag mode.
207                // This change updates the states of icons and text colors.
208                // Additional drawable states are updated by the framework
209                // based on the DragEvent.
210                setDragMode(true);
211            case DragEvent.ACTION_DRAG_ENTERED:
212            case DragEvent.ACTION_DRAG_EXITED:
213                // All of these states return based on isDroppableTarget's return value.
214                // If modifying, watch the switch's drop-through effects.
215                return isDroppableTarget(event);
216            case DragEvent.ACTION_DRAG_ENDED:
217                // Set drag mode to false since we're leaving drag mode.
218                // Updates all the states of icons and text colors back to non-drag values.
219                setDragMode(false);
220                return true;
221
222            case DragEvent.ACTION_DRAG_LOCATION:
223                return true;
224
225            case DragEvent.ACTION_DROP:
226                if (mDropHandler == null) {
227                    return false;
228                }
229
230                mDropHandler.handleDrop(event, mFolder);
231                return true;
232        }
233        return false;
234    }
235
236    @Override
237    protected int[] onCreateDrawableState(int extraSpace) {
238        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
239        if (mIsDragMode) {
240            mergeDrawableStates(drawableState, STATE_DRAG_MODE);
241        }
242        return drawableState;
243    }
244
245    private void setDragMode(boolean isDragMode) {
246        mIsDragMode = isDragMode;
247        refreshDrawableState();
248    }
249}
250