1 /* 2 * Copyright (C) 2013 The Android Open Source Project 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 17 18 package com.android.mail.adapter; 19 20 import com.android.mail.R; 21 import com.android.mail.providers.Account; 22 import com.android.mail.providers.Folder; 23 import com.android.mail.ui.AccountItemView; 24 import com.android.mail.ui.ControllableActivity; 25 import com.android.mail.ui.FolderItemView; 26 import com.android.mail.utils.FolderUri; 27 import com.android.mail.utils.LogTag; 28 import com.android.mail.utils.LogUtils; 29 30 import android.support.v4.text.BidiFormatter; 31 import android.view.LayoutInflater; 32 import android.view.View; 33 import android.view.ViewGroup; 34 import android.widget.TextView; 35 36 /** 37 * An element that is shown in the {@link com.android.mail.ui.FolderListFragment}. This class is 38 * only used for elements that are shown in the {@link com.android.mail.ui.DrawerFragment}. 39 * This class is an enumeration of a few element types: Account, a folder, a recent folder, 40 * or a header (a resource string). A {@link DrawerItem} can only be one type and can never 41 * switch types. Items are created using methods like 42 * {@link DrawerItem#ofAccount(com.android.mail.ui.ControllableActivity, 43 com.android.mail.providers.Account, int, boolean, BidiFormatter)}, 44 * {@link DrawerItem#ofWaitView(com.android.mail.ui.ControllableActivity, BidiFormatter)}, etc. 45 * 46 * Once created, the item can create a view using 47 * {@link #getView(android.view.View, android.view.ViewGroup)}. 48 */ 49 public class DrawerItem { 50 private static final String LOG_TAG = LogTag.getLogTag(); 51 public final Folder mFolder; 52 public final Account mAccount; 53 public final int mResource; 54 /** True if the drawer item represents the current account, false otherwise */ 55 public boolean mIsSelected; 56 /** Either {@link #VIEW_ACCOUNT}, {@link #VIEW_FOLDER} or {@link #VIEW_HEADER} */ 57 public final int mType; 58 /** A normal folder, also a child, if a parent is specified. */ 59 public static final int VIEW_FOLDER = 0; 60 /** A text-label which serves as a header in sectioned lists. */ 61 public static final int VIEW_HEADER = 1; 62 /** An account object, which allows switching accounts rather than folders. */ 63 public static final int VIEW_ACCOUNT = 2; 64 /** An expandable object for expanding/collapsing more of the list */ 65 public static final int VIEW_WAITING_FOR_SYNC = 3; 66 /** The value (1-indexed) of the last View type. Useful when returning the number of types. */ 67 private static final int LAST_FIELD = VIEW_WAITING_FOR_SYNC + 1; 68 69 /** TODO: On adding another type, be sure to change getViewTypes() */ 70 71 /** The parent activity */ 72 private final ControllableActivity mActivity; 73 private final LayoutInflater mInflater; 74 private final BidiFormatter mBidiFormatter; 75 76 // TODO(viki): Put all these constants in an interface. 77 /** 78 * Either {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT} or {@link #FOLDER_OTHER} when 79 * {@link #mType} is {@link #VIEW_FOLDER}, or an {@link #ACCOUNT} in the case of 80 * accounts, and {@link #INERT_HEADER} otherwise. 81 */ 82 public final int mFolderType; 83 /** Non existent item or folder type not yet set */ 84 public static final int UNSET = 0; 85 /** An unclickable text-header visually separating the different types. */ 86 public static final int INERT_HEADER = 0; 87 /** An inbox folder: Inbox, ...*/ 88 public static final int FOLDER_INBOX = 1; 89 /** A folder from whom a conversation was recently viewed */ 90 public static final int FOLDER_RECENT = 2; 91 /** A non-inbox folder that is shown in the "everything else" group. */ 92 public static final int FOLDER_OTHER = 3; 93 /** An entry for the accounts the user has on the device. */ 94 public static final int ACCOUNT = 4; 95 96 /** True if this view is enabled, false otherwise. */ 97 private final boolean mIsEnabled; 98 99 @Override 100 public String toString() { 101 switch(mType) { 102 case VIEW_FOLDER: 103 return folderToString(); 104 case VIEW_HEADER: 105 return headerToString(); 106 case VIEW_ACCOUNT: 107 return accountToString(); 108 case VIEW_WAITING_FOR_SYNC: 109 return waitToString(); 110 } 111 // Should never come here. 112 return null; 113 } 114 115 /** 116 * Creates a drawer item with every instance variable specified. 117 * 118 * @param type the type of the item. This must be a VIEW_* element 119 * @param activity the underlying activity 120 * @param folder a non-null folder, if this is a folder type 121 * @param folderType the type of the folder. For folders this is: 122 * {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT}, 123 * {@link #FOLDER_OTHER}, or for non-folders this is 124 * {@link #ACCOUNT}, or {@link #INERT_HEADER} 125 * @param account the account object, for an account drawer element 126 * @param resource either the string resource for a header, or the unread 127 * count for an account. 128 * @param isCurrentAccount true if this item is the current account 129 */ 130 private DrawerItem(int type, ControllableActivity activity, Folder folder, int folderType, 131 Account account, int resource, boolean isCurrentAccount, BidiFormatter bidiFormatter) { 132 mActivity = activity; 133 mFolder = folder; 134 mFolderType = folderType; 135 mAccount = account; 136 mResource = resource; 137 mIsSelected = isCurrentAccount; 138 mInflater = LayoutInflater.from(activity.getActivityContext()); 139 mType = type; 140 mIsEnabled = calculateEnabled(); 141 mBidiFormatter = bidiFormatter; 142 } 143 144 /** 145 * Create a folder item with the given type. 146 * 147 * @param activity the underlying activity 148 * @param folder a folder that this item represents 149 * @param folderType one of {@link #FOLDER_INBOX}, {@link #FOLDER_RECENT} or 150 * {@link #FOLDER_OTHER} 151 * @return a drawer item for the folder. 152 */ 153 public static DrawerItem ofFolder(ControllableActivity activity, Folder folder, 154 int folderType, BidiFormatter bidiFormatter) { 155 return new DrawerItem(VIEW_FOLDER, activity, folder, folderType, null, -1, false, 156 bidiFormatter); 157 } 158 159 private String folderToString() { 160 final StringBuilder sb = new StringBuilder("[DrawerItem "); 161 sb.append(" VIEW_FOLDER "); 162 sb.append(", mFolder="); 163 sb.append(mFolder); 164 sb.append(", mFolderType="); 165 sb.append(mFolderType); 166 sb.append("]"); 167 return sb.toString(); 168 } 169 170 /** 171 * Creates an item from an account. 172 * @param activity the underlying activity 173 * @param account the account to create a drawer item for 174 * @param unreadCount the unread count of the account, pass zero if 175 * @param isCurrentAccount true if the account is the current account, false otherwise 176 * @return a drawer item for the account. 177 */ 178 public static DrawerItem ofAccount(ControllableActivity activity, Account account, 179 int unreadCount, boolean isCurrentAccount, BidiFormatter bidiFormatter) { 180 return new DrawerItem(VIEW_ACCOUNT, activity, null, ACCOUNT, account, unreadCount, 181 isCurrentAccount, bidiFormatter); 182 } 183 184 private String accountToString() { 185 final StringBuilder sb = new StringBuilder("[DrawerItem "); 186 sb.append(" VIEW_ACCOUNT "); 187 sb.append(", mAccount="); 188 sb.append(mAccount); 189 sb.append("]"); 190 return sb.toString(); 191 } 192 193 /** 194 * Create a header item with a string resource. 195 * 196 * @param activity the underlying activity 197 * @param resource the string resource: R.string.all_folders_heading 198 * @return a drawer item for the header. 199 */ 200 public static DrawerItem ofHeader(ControllableActivity activity, int resource, 201 BidiFormatter bidiFormatter) { 202 return new DrawerItem(VIEW_HEADER, activity, null, INERT_HEADER, null, resource, false, 203 bidiFormatter); 204 } 205 206 private String headerToString() { 207 final StringBuilder sb = new StringBuilder("[DrawerItem "); 208 sb.append(" VIEW_HEADER "); 209 sb.append(", mResource="); 210 sb.append(mResource); 211 sb.append("]"); 212 return sb.toString(); 213 } 214 215 /** 216 * Create a "waiting for initialization" item. 217 * 218 * @param activity the underlying activity 219 * @return a drawer item with an indeterminate progress indicator. 220 */ 221 public static DrawerItem ofWaitView(ControllableActivity activity, 222 BidiFormatter bidiFormatter) { 223 return new DrawerItem(VIEW_WAITING_FOR_SYNC, activity, null, INERT_HEADER, null, -1, false, 224 bidiFormatter); 225 } 226 227 private static String waitToString() { 228 return "[DrawerItem VIEW_WAITING_FOR_SYNC ]"; 229 } 230 231 /** 232 * Returns a view for the given item. The method signature is identical to that required by a 233 * {@link android.widget.ListAdapter#getView(int, android.view.View, android.view.ViewGroup)}. 234 */ 235 public View getView(View convertView, ViewGroup parent) { 236 final View result; 237 switch (mType) { 238 case VIEW_FOLDER: 239 result = getFolderView(convertView, parent); 240 break; 241 case VIEW_HEADER: 242 result = getHeaderView(convertView, parent); 243 break; 244 case VIEW_ACCOUNT: 245 result = getAccountView(convertView, parent); 246 break; 247 case VIEW_WAITING_FOR_SYNC: 248 result = getEmptyView(convertView, parent); 249 break; 250 default: 251 LogUtils.wtf(LOG_TAG, "DrawerItem.getView(%d) for an invalid type!", mType); 252 result = null; 253 } 254 return result; 255 } 256 257 /** 258 * Book-keeping for how many different view types there are. Be sure to 259 * increment this appropriately once adding more types as drawer items 260 * @return number of different types of view items 261 */ 262 public static int getViewTypes() { 263 return LAST_FIELD; 264 } 265 266 /** 267 * Returns whether this view is enabled or not. An enabled view is one that accepts user taps 268 * and acts upon them. 269 * @return true if this view is enabled, false otherwise. 270 */ 271 public boolean isItemEnabled() { 272 return mIsEnabled; 273 } 274 275 /** Calculate whether the item is enabled */ 276 private boolean calculateEnabled() { 277 switch (mType) { 278 case VIEW_HEADER : 279 // Headers are never enabled. 280 return false; 281 case VIEW_FOLDER : 282 // Folders are always enabled. 283 return true; 284 case VIEW_ACCOUNT: 285 // Accounts are always enabled. 286 return true; 287 case VIEW_WAITING_FOR_SYNC: 288 // Waiting for sync cannot be tapped, so never enabled. 289 return false; 290 default: 291 LogUtils.wtf(LOG_TAG, "DrawerItem.isItemEnabled() for invalid type %d", mType); 292 return false; 293 } 294 } 295 296 /** 297 * Returns whether this view is highlighted or not. 298 * 299 * 300 * @param currentFolder The current folder, according to the 301 * {@link com.android.mail.ui.FolderListFragment} 302 * @param currentType The type of the current folder. We want to only highlight a folder once. 303 * A folder might be in two places at once: in "All Folders", and in 304 * "Recent Folder". Valid types of selected folders are : 305 * {@link DrawerItem#FOLDER_INBOX}, {@link DrawerItem#FOLDER_RECENT} or 306 * {@link DrawerItem#FOLDER_OTHER}, or {@link DrawerItem#UNSET}. 307 308 * @return true if this DrawerItem results in a view that is highlighted (this DrawerItem is 309 * the current folder. 310 */ 311 public boolean isHighlighted(FolderUri currentFolder, int currentType) { 312 switch (mType) { 313 case VIEW_HEADER : 314 // Headers are never highlighted 315 return false; 316 case VIEW_FOLDER: 317 // True if folder types and URIs are the same 318 if (currentFolder != null && mFolder != null && mFolder.folderUri != null) { 319 return (mFolderType == currentType) && mFolder.folderUri.equals(currentFolder); 320 } 321 return false; 322 case VIEW_ACCOUNT: 323 // Accounts are never highlighted 324 return false; 325 case VIEW_WAITING_FOR_SYNC: 326 // Waiting for sync cannot be tapped, so never highlighted. 327 return false; 328 default: 329 LogUtils.wtf(LOG_TAG, "DrawerItem.isHighlighted() for invalid type %d", mType); 330 return false; 331 } 332 } 333 334 /** 335 * Return a view for an account object. 336 * 337 * @param convertView a view, possibly null, to be recycled. 338 * @param parent the parent viewgroup to attach to. 339 * @return a view to display at this position. 340 */ 341 private View getAccountView(View convertView, ViewGroup parent) { 342 final AccountItemView accountItemView; 343 if (convertView != null) { 344 accountItemView = (AccountItemView) convertView; 345 } else { 346 accountItemView = 347 (AccountItemView) mInflater.inflate(R.layout.account_item, null, false); 348 } 349 accountItemView.bind(mAccount, mIsSelected, mResource); 350 View v = accountItemView.findViewById(R.id.account_graphic); 351 v.setBackgroundColor(mAccount.color); 352 return accountItemView; 353 } 354 355 /** 356 * Returns a text divider between divisions. 357 * 358 * @param convertView a previous view, perhaps null 359 * @param parent the parent of this view 360 * @return a text header at the given position. 361 */ 362 private View getHeaderView(View convertView, ViewGroup parent) { 363 final TextView headerView; 364 if (convertView != null) { 365 headerView = (TextView) convertView; 366 } else { 367 headerView = (TextView) mInflater.inflate( 368 R.layout.folder_list_header, parent, false); 369 } 370 headerView.setText(mResource); 371 return headerView; 372 } 373 374 /** 375 * Return a folder: either a parent folder or a normal (child or flat) 376 * folder. 377 * 378 * @param convertView a view, possibly null, to be recycled. 379 * @return a view showing a folder at the given position. 380 */ 381 private View getFolderView(View convertView, ViewGroup parent) { 382 final FolderItemView folderItemView; 383 if (convertView != null) { 384 folderItemView = (FolderItemView) convertView; 385 } else { 386 folderItemView = 387 (FolderItemView) mInflater.inflate(R.layout.folder_item, null, false); 388 } 389 folderItemView.bind(mFolder, mActivity, mBidiFormatter); 390 Folder.setFolderBlockColor(mFolder, folderItemView.findViewById(R.id.color_block)); 391 folderItemView.setIcon(mFolder); 392 return folderItemView; 393 } 394 395 /** 396 * Return a view for the 'Waiting for sync' item with the indeterminate progress indicator. 397 * 398 * @param convertView a view, possibly null, to be recycled. 399 * @param parent the parent hosting this view. 400 * @return a view for "Waiting for sync..." at given position. 401 */ 402 private View getEmptyView(View convertView, ViewGroup parent) { 403 final ViewGroup emptyView; 404 if (convertView != null) { 405 emptyView = (ViewGroup) convertView; 406 } else { 407 emptyView = (ViewGroup) mInflater.inflate(R.layout.drawer_empty_view, parent, false); 408 } 409 return emptyView; 410 } 411 412 } 413 414