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 */ 16 package com.android.mail.ui; 17 18 import android.content.Context; 19 import android.graphics.Color; 20 import android.support.v4.text.BidiFormatter; 21 import android.util.AttributeSet; 22 import android.view.DragEvent; 23 import android.view.View; 24 import android.widget.ImageView; 25 import android.widget.RelativeLayout; 26 import android.widget.TextView; 27 28 import com.android.mail.R; 29 import com.android.mail.providers.Folder; 30 import com.android.mail.utils.LogTag; 31 import com.android.mail.utils.LogUtils; 32 import com.android.mail.utils.Utils; 33 34 /** 35 * The view for each folder in the folder list. 36 */ 37 public 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