1 /* 2 * Copyright (C) 2012 Google Inc. 3 * Licensed to The Android Open Source Project. 4 * 5 * Licensed under the Apache License, Version 2.0 (the "License"); 6 * you may not use this file except in compliance with the License. 7 * You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package com.android.mail.browse; 19 20 import android.content.Context; 21 import android.content.res.Resources; 22 import android.os.Build; 23 import android.text.Spannable; 24 import android.text.SpannableStringBuilder; 25 import android.text.TextUtils; 26 import android.text.style.BackgroundColorSpan; 27 import android.text.style.ForegroundColorSpan; 28 import android.util.AttributeSet; 29 import android.util.TypedValue; 30 import android.view.View; 31 import android.view.View.OnClickListener; 32 import android.view.ViewGroup; 33 import android.widget.LinearLayout; 34 import android.widget.TextView; 35 36 import com.android.mail.R; 37 import com.android.mail.browse.ConversationViewAdapter.ConversationHeaderItem; 38 import com.android.mail.browse.FolderSpan.FolderSpanDimensions; 39 import com.android.mail.providers.Conversation; 40 import com.android.mail.providers.Folder; 41 import com.android.mail.providers.Settings; 42 import com.android.mail.ui.FolderDisplayer; 43 import com.android.mail.utils.LogTag; 44 import com.android.mail.utils.LogUtils; 45 import com.android.mail.utils.Utils; 46 47 /** 48 * A view for the subject and folders in the conversation view. This container 49 * makes an attempt to combine subject and folders on the same horizontal line if 50 * there is enough room to fit both without wrapping. If they overlap, it 51 * adjusts the layout to position the folders below the subject. 52 */ 53 public class ConversationViewHeader extends LinearLayout implements OnClickListener { 54 55 public interface ConversationViewHeaderCallbacks { 56 /** 57 * Called in response to a click on the folders region. 58 */ 59 void onFoldersClicked(); 60 61 /** 62 * Called when the height of the {@link ConversationViewHeader} changes. 63 * 64 * @param newHeight the new height in px 65 */ 66 void onConversationViewHeaderHeightChange(int newHeight); 67 } 68 69 private static final String LOG_TAG = LogTag.getLogTag(); 70 private TextView mSubjectView; 71 private FolderSpanTextView mFoldersView; 72 private ConversationViewHeaderCallbacks mCallbacks; 73 private ConversationAccountController mAccountController; 74 private ConversationFolderDisplayer mFolderDisplayer; 75 private ConversationHeaderItem mHeaderItem; 76 77 private boolean mLargeText; 78 private final float mCondensedTextSize; 79 private final int mCondensedTopPadding; 80 81 /** 82 * Instantiated from this layout: conversation_view_header.xml 83 * @param context 84 */ 85 public ConversationViewHeader(Context context) { 86 this(context, null); 87 } 88 89 public ConversationViewHeader(Context context, AttributeSet attrs) { 90 super(context, attrs); 91 mLargeText = true; 92 final Resources resources = getResources(); 93 mCondensedTextSize = 94 resources.getDimensionPixelSize(R.dimen.conversation_header_font_size_condensed); 95 mCondensedTopPadding = resources.getDimensionPixelSize( 96 R.dimen.conversation_header_vertical_padding_condensed); 97 } 98 99 @Override 100 protected void onFinishInflate() { 101 super.onFinishInflate(); 102 103 mSubjectView = (TextView) findViewById(R.id.subject); 104 mFoldersView = (FolderSpanTextView) findViewById(R.id.folders); 105 106 mFoldersView.setOnClickListener(this); 107 mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView); 108 } 109 110 @Override 111 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 112 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 113 114 // If we currently have large text and we have greater than 2 lines, 115 // switch to smaller text size with smaller top padding and re-measure 116 if (mLargeText && mSubjectView.getLineCount() > 2) { 117 mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCondensedTextSize); 118 119 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) { 120 // start, top, end, bottom 121 mSubjectView.setPaddingRelative(mSubjectView.getPaddingStart(), 122 mCondensedTopPadding, mSubjectView.getPaddingEnd(), 123 mSubjectView.getPaddingBottom()); 124 } else { 125 mSubjectView.setPadding(mSubjectView.getPaddingLeft(), 126 mCondensedTopPadding, mSubjectView.getPaddingRight(), 127 mSubjectView.getPaddingBottom()); 128 } 129 130 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 131 } 132 } 133 134 public void setCallbacks(ConversationViewHeaderCallbacks callbacks, 135 ConversationAccountController accountController) { 136 mCallbacks = callbacks; 137 mAccountController = accountController; 138 } 139 140 public void setSubject(final String subject) { 141 mSubjectView.setText(subject); 142 if (TextUtils.isEmpty(subject)) { 143 mSubjectView.setVisibility(GONE); 144 } 145 } 146 147 public void setFoldersVisible(boolean show) { 148 mFoldersView.setVisibility(show ? View.VISIBLE : View.GONE); 149 } 150 151 public void setFolders(Conversation conv) { 152 setFoldersVisible(true); 153 SpannableStringBuilder sb = new SpannableStringBuilder(); 154 final Settings settings = mAccountController.getAccount().settings; 155 if (settings.priorityArrowsEnabled && conv.isImportant()) { 156 sb.append('.'); 157 sb.setSpan(new PriorityIndicatorSpan(getContext(), 158 R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0, 159 mFoldersView.getPaddingAbove()), 160 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 161 } 162 163 mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */, 164 -1 /* ignoreFolderType */); 165 mFolderDisplayer.appendFolderSpans(sb); 166 167 mFoldersView.setText(sb); 168 } 169 170 public void bind(ConversationHeaderItem headerItem) { 171 mHeaderItem = headerItem; 172 } 173 174 private int measureHeight() { 175 ViewGroup parent = (ViewGroup) getParent(); 176 if (parent == null) { 177 LogUtils.e(LOG_TAG, "Unable to measure height of conversation header"); 178 return getHeight(); 179 } 180 final int h = Utils.measureViewHeight(this, parent); 181 return h; 182 } 183 184 /** 185 * Update the conversation view header to reflect the updated conversation. 186 */ 187 public void onConversationUpdated(Conversation conv) { 188 // The only things we have to worry about when the conversation changes 189 // in the conversation header are the folders and priority indicators. 190 // Updating these will resize the space for the header. 191 setFolders(conv); 192 if (mHeaderItem != null) { 193 final int h = measureHeight(); 194 if (mHeaderItem.setHeight(h)) { 195 mCallbacks.onConversationViewHeaderHeightChange(h); 196 } 197 } 198 } 199 200 @Override 201 public void onClick(View v) { 202 if (R.id.folders == v.getId()) { 203 if (mCallbacks != null) { 204 mCallbacks.onFoldersClicked(); 205 } 206 } 207 } 208 209 private static class ConversationFolderDisplayer extends FolderDisplayer { 210 211 private FolderSpanDimensions mDims; 212 213 public ConversationFolderDisplayer(Context context, FolderSpanDimensions dims) { 214 super(context); 215 mDims = dims; 216 } 217 218 public void appendFolderSpans(SpannableStringBuilder sb) { 219 for (final Folder f : mFoldersSortedSet) { 220 final int bgColor = f.getBackgroundColor(mDefaultBgColor); 221 final int fgColor = f.getForegroundColor(mDefaultFgColor); 222 addSpan(sb, f.name, bgColor, fgColor); 223 } 224 225 if (mFoldersSortedSet.isEmpty()) { 226 final Resources r = mContext.getResources(); 227 final String name = r.getString(R.string.add_label); 228 final int bgColor = r.getColor(R.color.conv_header_add_label_background); 229 final int fgColor = r.getColor(R.color.conv_header_add_label_text); 230 addSpan(sb, name, bgColor, fgColor); 231 } 232 } 233 234 private void addSpan(SpannableStringBuilder sb, String name, int bgColor, 235 int fgColor) { 236 final int start = sb.length(); 237 sb.append(name); 238 final int end = sb.length(); 239 240 sb.setSpan(new BackgroundColorSpan(bgColor), start, end, 241 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 242 sb.setSpan(new ForegroundColorSpan(fgColor), start, end, 243 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 244 sb.setSpan(new FolderSpan(sb, mDims), start, end, 245 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 246 } 247 248 } 249 } 250