ConversationViewHeader.java revision 67aa9e5162a15fb8b46b4113ac627cd20668f095
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 18package com.android.mail.browse; 19 20import android.content.Context; 21import android.content.res.Resources; 22import android.text.Spannable; 23import android.text.SpannableStringBuilder; 24import android.text.TextUtils; 25import android.text.style.BackgroundColorSpan; 26import android.text.style.ForegroundColorSpan; 27import android.util.AttributeSet; 28import android.view.View; 29import android.view.ViewGroup; 30import android.view.View.OnClickListener; 31import android.widget.RelativeLayout; 32import android.widget.TextView; 33 34import com.android.mail.R; 35import com.android.mail.browse.ConversationViewAdapter.ConversationHeaderItem; 36import com.android.mail.browse.FolderSpan.FolderSpanDimensions; 37import com.android.mail.providers.Conversation; 38import com.android.mail.providers.Folder; 39import com.android.mail.providers.Settings; 40import com.android.mail.ui.FolderDisplayer; 41import com.android.mail.utils.LogTag; 42import com.android.mail.utils.LogUtils; 43import com.android.mail.utils.Utils; 44 45/** 46 * A view for the subject and folders in the conversation view. This container 47 * makes an attempt to combine subject and folders on the same horizontal line if 48 * there is enough room to fit both without wrapping. If they overlap, it 49 * adjusts the layout to position the folders below the subject. 50 */ 51public class ConversationViewHeader extends RelativeLayout implements OnClickListener { 52 53 public interface ConversationViewHeaderCallbacks { 54 /** 55 * Called in response to a click on the folders region. 56 */ 57 void onFoldersClicked(); 58 59 /** 60 * Called when the height of the {@link ConversationViewHeader} changes. 61 * 62 * @param newHeight the new height in px 63 */ 64 void onConversationViewHeaderHeightChange(int newHeight); 65 66 /** 67 * Measure a subject string for display outside a conversation view and 68 * return the substring of trailing characters that didn't fit. Should 69 * not actually render the text, just measure it. 70 * 71 * @param subject string to measure 72 * @return the remainder of text that didn't fit 73 */ 74 String getSubjectRemainder(String subject); 75 } 76 77 private static final String LOG_TAG = LogTag.getLogTag(); 78 private TextView mSubjectView; 79 private FolderSpanTextView mFoldersView; 80 private ConversationViewHeaderCallbacks mCallbacks; 81 private ConversationAccountController mAccountController; 82 private ConversationFolderDisplayer mFolderDisplayer; 83 private ConversationHeaderItem mHeaderItem; 84 85 public ConversationViewHeader(Context context) { 86 this(context, null); 87 } 88 89 public ConversationViewHeader(Context context, AttributeSet attrs) { 90 super(context, attrs); 91 } 92 93 @Override 94 protected void onFinishInflate() { 95 super.onFinishInflate(); 96 97 mSubjectView = (TextView) findViewById(R.id.subject); 98 mFoldersView = (FolderSpanTextView) findViewById(R.id.folders); 99 100 mFoldersView.setOnClickListener(this); 101 mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView); 102 } 103 104 @Override 105 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 106 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 107 108 // reposition the folders if they don't fit horizontally next to the 109 // subject 110 // (taking into account child margins and parent padding) 111 final int childWidthSum = getTotalMeasuredChildWidth(mSubjectView) 112 + getTotalMeasuredChildWidth(mFoldersView) + getPaddingLeft() + getPaddingRight(); 113 114 if (childWidthSum > getMeasuredWidth()) { 115 LayoutParams params = (LayoutParams) mFoldersView.getLayoutParams(); 116 params.addRule(RelativeLayout.BELOW, R.id.subject); 117 params.addRule(RelativeLayout.ALIGN_BASELINE, 0); 118 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 119 } 120 } 121 122 private static int getTotalMeasuredChildWidth(View child) { 123 final LayoutParams p = (LayoutParams) child.getLayoutParams(); 124 return child.getMeasuredWidth() + p.leftMargin + p.rightMargin; 125 } 126 127 public void setCallbacks(ConversationViewHeaderCallbacks callbacks, 128 ConversationAccountController accountController) { 129 mCallbacks = callbacks; 130 mAccountController = accountController; 131 } 132 133 public void setSubject(final String subject) { 134 String subjectToShow = subject; 135 if (mCallbacks != null && mCallbacks.getSubjectRemainder(subject) == null) { 136 subjectToShow = null; 137 } 138 mSubjectView.setText(subjectToShow); 139 140 if (TextUtils.isEmpty(subjectToShow)) { 141 mSubjectView.setVisibility(GONE); 142 } 143 } 144 145 public void setFoldersVisible(boolean show) { 146 mFoldersView.setVisibility(show ? View.VISIBLE : View.GONE); 147 } 148 149 public void setFolders(Conversation conv) { 150 setFoldersVisible(true); 151 SpannableStringBuilder sb = new SpannableStringBuilder(); 152 final Settings settings = mAccountController.getAccount().settings; 153 if (settings.priorityArrowsEnabled && conv.isImportant()) { 154 sb.append('.'); 155 sb.setSpan(new PriorityIndicatorSpan(getContext(), 156 R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0), 157 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 158 } 159 160 mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */, 161 -1 /* ignoreFolderType */); 162 mFolderDisplayer.appendFolderSpans(sb); 163 164 mFoldersView.setText(sb); 165 } 166 167 public void bind(ConversationHeaderItem headerItem) { 168 mHeaderItem = headerItem; 169 } 170 171 private int measureHeight() { 172 ViewGroup parent = (ViewGroup) getParent(); 173 if (parent == null) { 174 LogUtils.e(LOG_TAG, "Unable to measure height of conversation header"); 175 return getHeight(); 176 } 177 final int h = Utils.measureViewHeight(this, parent); 178 return h; 179 } 180 181 /** 182 * Update the conversation view header to reflect the updated conversation. 183 */ 184 public void onConversationUpdated(Conversation conv) { 185 // The only things we have to worry about when the conversation changes 186 // in the conversation header are the folders and priority indicators. 187 // Updating these will resize the space for the header. 188 setFolders(conv); 189 if (mHeaderItem != null) { 190 final int h = measureHeight(); 191 if (mHeaderItem.setHeight(h)) { 192 mCallbacks.onConversationViewHeaderHeightChange(h); 193 } 194 } 195 } 196 197 @Override 198 public void onClick(View v) { 199 if (R.id.folders == v.getId()) { 200 if (mCallbacks != null) { 201 mCallbacks.onFoldersClicked(); 202 } 203 } 204 } 205 206 private static class ConversationFolderDisplayer extends FolderDisplayer { 207 208 private FolderSpanDimensions mDims; 209 210 public ConversationFolderDisplayer(Context context, FolderSpanDimensions dims) { 211 super(context); 212 mDims = dims; 213 } 214 215 public void appendFolderSpans(SpannableStringBuilder sb) { 216 for (final Folder f : mFoldersSortedSet) { 217 final int bgColor = Folder.getNonEmptyColor(f.bgColor, mDefaultBgColor); 218 final int fgColor = Folder.getNonEmptyColor(f.fgColor, mDefaultFgColor); 219 addSpan(sb, f.name, bgColor, fgColor); 220 } 221 222 if (mFoldersSortedSet.isEmpty()) { 223 final Resources r = mContext.getResources(); 224 final String name = r.getString(R.string.add_label); 225 final int bgColor = r.getColor(R.color.conv_header_add_label_background); 226 final int fgColor = r.getColor(R.color.conv_header_add_label_text); 227 addSpan(sb, name, bgColor, fgColor); 228 } 229 } 230 231 private void addSpan(SpannableStringBuilder sb, String name, int bgColor, 232 int fgColor) { 233 final int start = sb.length(); 234 sb.append(name); 235 final int end = sb.length(); 236 237 sb.setSpan(new BackgroundColorSpan(bgColor), start, end, 238 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 239 sb.setSpan(new ForegroundColorSpan(fgColor), start, end, 240 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 241 sb.setSpan(new FolderSpan(sb, mDims), start, end, 242 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 243 } 244 245 } 246} 247