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.support.v4.text.BidiFormatter;
21import android.util.AttributeSet;
22import android.view.DragEvent;
23import android.view.View;
24import android.widget.ImageView;
25import android.widget.RelativeLayout;
26import android.widget.TextView;
27
28import com.android.mail.R;
29import com.android.mail.providers.Folder;
30import com.android.mail.utils.LogTag;
31import com.android.mail.utils.LogUtils;
32import com.android.mail.utils.Utils;
33
34/**
35 * The view for each folder in the folder list.
36 */
37public class FolderItemView extends RelativeLayout {
38    private final String LOG_TAG = LogTag.getLogTag();
39
40    private static final int[] STATE_DRAG_MODE = {R.attr.state_drag_mode};
41
42    private Folder mFolder;
43    private TextView mFolderTextView;
44    private TextView mUnreadCountTextView;
45    private TextView mUnseenCountTextView;
46    private DropHandler mDropHandler;
47    private ImageView mFolderParentIcon;
48
49    private boolean mIsDragMode;
50
51    /**
52     * A delegate for a handler to handle a drop of an item.
53     */
54    public interface DropHandler {
55        /**
56         * Return whether or not the drag event is supported by the drop handler. The
57         *     {@code FolderItemView} will present appropriate visual affordances if the drag is
58         *     supported.
59         */
60        boolean supportsDrag(DragEvent event, Folder folder);
61
62        /**
63         * Handles a drop event, applying the appropriate logic.
64         */
65        void handleDrop(DragEvent event, Folder folder);
66    }
67
68    public FolderItemView(Context context) {
69        super(context);
70    }
71
72    public FolderItemView(Context context, AttributeSet attrs) {
73        super(context, attrs);
74    }
75
76    public FolderItemView(Context context, AttributeSet attrs, int defStyle) {
77        super(context, attrs, defStyle);
78
79        mIsDragMode = false;
80    }
81
82    @Override
83    protected void onFinishInflate() {
84        super.onFinishInflate();
85
86        mFolderTextView = (TextView)findViewById(R.id.name);
87        mUnreadCountTextView = (TextView)findViewById(R.id.unread);
88        mUnseenCountTextView = (TextView)findViewById(R.id.unseen);
89        mFolderParentIcon = (ImageView) findViewById(R.id.folder_parent_icon);
90    }
91
92    /**
93     * Returns true if the two folders lead to identical {@link FolderItemView} objects.
94     * @param a
95     * @param b
96     * @return true if the two folders would still lead to the same {@link FolderItemView}.
97     */
98    public static boolean areSameViews(final Folder a, final Folder b) {
99        if (a == null) {
100            return b == null;
101        }
102        if (b == null) {
103            // a is not null because it would have returned above.
104            return false;
105        }
106        return (a == b || (a.folderUri.equals(b.folderUri)
107                && a.name.equals(b.name)
108                && a.hasChildren == b.hasChildren
109                && a.unseenCount == b.unseenCount
110                && a.unreadCount == b.unreadCount));
111    }
112
113    public void bind(final Folder folder, final DropHandler dropHandler,
114            final BidiFormatter bidiFormatter) {
115        mFolder = folder;
116        mDropHandler = dropHandler;
117
118        mFolderTextView.setText(bidiFormatter.unicodeWrap(folder.name));
119
120        mFolderParentIcon.setVisibility(mFolder.hasChildren ? View.VISIBLE : View.GONE);
121        if (mFolder.isInbox() && mFolder.unseenCount > 0) {
122            mUnreadCountTextView.setVisibility(View.GONE);
123            setUnseenCount(mFolder.getBackgroundColor(Color.BLACK), mFolder.unseenCount);
124        } else {
125            mUnseenCountTextView.setVisibility(View.GONE);
126            setUnreadCount(Utils.getFolderUnreadDisplayCount(mFolder));
127        }
128    }
129
130    /**
131     * Sets the icon, if any. If the image view's visibility is set to gone, the text view will
132     * be moved over to account for the change.
133     */
134    public void setIcon(final Folder folder) {
135        final ImageView folderIconView = (ImageView) findViewById(R.id.folder_icon);
136        Folder.setIcon(folder, folderIconView);
137        if (folderIconView.getVisibility() == View.GONE) {
138            mFolderTextView.setPadding(getContext()
139                    .getResources().getDimensionPixelSize(R.dimen.folder_list_item_left_offset),
140                    0, 0, 0 /* No top, right, bottom padding needed */);
141        } else {
142            // View recycling case
143            mFolderTextView.setPadding(0, 0, 0, 0);
144        }
145    }
146
147    /**
148     * Sets the unread count, taking care to hide/show the textview if the count is zero/non-zero.
149     */
150    private void setUnreadCount(int count) {
151        mUnreadCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
152        if (count > 0) {
153            mUnreadCountTextView.setText(Utils.getUnreadCountString(getContext(), count));
154        }
155    }
156
157    /**
158     * Sets the unseen count, taking care to hide/show the textview if the count is zero/non-zero.
159     */
160    private void setUnseenCount(final int color, final int count) {
161        mUnseenCountTextView.setVisibility(count > 0 ? View.VISIBLE : View.GONE);
162        if (count > 0) {
163            mUnseenCountTextView.setBackgroundColor(color);
164            mUnseenCountTextView.setText(Utils.getUnreadCountString(getContext(), count));
165        }
166    }
167
168    /**
169     * Used if we detect a problem with the unread count and want to force an override.
170     * @param count
171     */
172    public final void overrideUnreadCount(int count) {
173        LogUtils.e(LOG_TAG, "FLF->FolderItem.getFolderView: unread count mismatch found (%s vs %d)",
174                mUnreadCountTextView.getText(), count);
175        setUnreadCount(count);
176    }
177
178    private boolean isDroppableTarget(DragEvent event) {
179        return (mDropHandler != null && mDropHandler.supportsDrag(event, mFolder));
180    }
181
182    /**
183     * Handles the drag event.
184     *
185     * @param event the drag event to be handled
186     */
187    @Override
188    public boolean onDragEvent(DragEvent event) {
189        switch (event.getAction()) {
190            case DragEvent.ACTION_DRAG_STARTED:
191                // Set drag mode state to true now that we have entered drag mode.
192                // This change updates the states of icons and text colors.
193                // Additional drawable states are updated by the framework
194                // based on the DragEvent.
195                setDragMode(true);
196            case DragEvent.ACTION_DRAG_ENTERED:
197            case DragEvent.ACTION_DRAG_EXITED:
198                // All of these states return based on isDroppableTarget's return value.
199                // If modifying, watch the switch's drop-through effects.
200                return isDroppableTarget(event);
201            case DragEvent.ACTION_DRAG_ENDED:
202                // Set drag mode to false since we're leaving drag mode.
203                // Updates all the states of icons and text colors back to non-drag values.
204                setDragMode(false);
205                return true;
206
207            case DragEvent.ACTION_DRAG_LOCATION:
208                return true;
209
210            case DragEvent.ACTION_DROP:
211                if (mDropHandler == null) {
212                    return false;
213                }
214
215                mDropHandler.handleDrop(event, mFolder);
216                return true;
217        }
218        return false;
219    }
220
221    @Override
222    protected int[] onCreateDrawableState(int extraSpace) {
223        final int[] drawableState = super.onCreateDrawableState(extraSpace + 1);
224        if (mIsDragMode) {
225            mergeDrawableStates(drawableState, STATE_DRAG_MODE);
226        }
227        return drawableState;
228    }
229
230    private void setDragMode(boolean isDragMode) {
231        mIsDragMode = isDragMode;
232        refreshDrawableState();
233    }
234}
235