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