ConversationViewHeader.java revision 5ff63747a1b5c6e2197528972cbc3ba808b09d8d
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.view.ViewGroup; 31import android.widget.RelativeLayout; 32import android.widget.TextView; 33 34import com.android.mail.R; 35import com.android.mail.browse.FolderSpan.FolderSpanDimensions; 36import com.android.mail.providers.Conversation; 37import com.android.mail.providers.Folder; 38import com.android.mail.ui.FolderDisplayer; 39import com.android.mail.utils.Utils; 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 String mSubject; 74 private TextView mSubjectView; 75 private FolderSpanTextView mFoldersView; 76 private ConversationViewHeaderCallbacks mCallbacks; 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 90 @Override 91 protected void onFinishInflate() { 92 super.onFinishInflate(); 93 94 mSubjectView = (TextView) findViewById(R.id.subject); 95 mFoldersView = (FolderSpanTextView) findViewById(R.id.folders); 96 97 mFoldersView.setOnClickListener(this); 98 mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView); 99 } 100 101 @Override 102 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 103 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 104 105 // reposition the folders if they don't fit horizontally next to the 106 // subject 107 // (taking into account child margins and parent padding) 108 final int childWidthSum = getTotalMeasuredChildWidth(mSubjectView) 109 + getTotalMeasuredChildWidth(mFoldersView) + getPaddingLeft() + getPaddingRight(); 110 111 if (childWidthSum > getMeasuredWidth()) { 112 LayoutParams params = (LayoutParams) mFoldersView.getLayoutParams(); 113 params.addRule(RelativeLayout.BELOW, R.id.subject); 114 params.addRule(RelativeLayout.ALIGN_BASELINE, 0); 115 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 116 } 117 } 118 119 private static int getTotalMeasuredChildWidth(View child) { 120 LayoutParams p = (LayoutParams) child.getLayoutParams(); 121 return child.getMeasuredWidth() + p.leftMargin + p.rightMargin; 122 } 123 124 public void setCallbacks(ConversationViewHeaderCallbacks callbacks) { 125 mCallbacks = callbacks; 126 } 127 128 public void setSubject(final String subject, boolean notify) { 129 mSubject = subject; 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 public String getSubject() { 146 return mSubject; 147 } 148 149 public void setFolders(Conversation conv, boolean notify) { 150 SpannableStringBuilder sb = new SpannableStringBuilder(); 151 152 // TODO: read 'show priority arrows' pref from settings 153 final boolean importanceArrowsEnabled = true; 154 // TODO: read importance value from Conversation 155 if (importanceArrowsEnabled && true /* conv.isImportant */) { 156 sb.append('.'); 157 sb.setSpan(new PriorityIndicatorSpan(getContext(), 158 R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0), 159 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 160 } 161 162 mFolderDisplayer.loadConversationFolders(conv.rawFolders, null /* ignoreFolder */); 163 mFolderDisplayer.appendFolderSpans(sb); 164 165 mFoldersView.setText(sb); 166 167 if (notify) { 168 handleSizeChanged(); 169 } 170 } 171 172 public int getPremeasuredHeight() { 173 ViewGroup parent = (ViewGroup) getParent(); 174 return Utils.measureViewHeight(this, parent); 175 } 176 177 @Override 178 public void onClick(View v) { 179 if (R.id.folders == v.getId()) { 180 if (mCallbacks != null) { 181 mCallbacks.onFoldersClicked(); 182 } 183 } 184 } 185 186 private void handleSizeChanged() { 187 mSizeChanged = true; 188 } 189 190 @Override 191 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 192 super.onSizeChanged(w, h, oldw, oldh); 193 194 if (mSizeChanged) { 195 // propagate new size to webview conversation header spacer 196 // only do this for known size changes 197 if (mCallbacks != null) { 198 mCallbacks.onConversationViewHeaderHeightChange(h); 199 } 200 201 mSizeChanged = false; 202 } 203 } 204 205 private static class ConversationFolderDisplayer extends FolderDisplayer { 206 207 private FolderSpanDimensions mDims; 208 209 public ConversationFolderDisplayer(Context context, FolderSpanDimensions dims) { 210 super(context); 211 mDims = dims; 212 } 213 214 public void appendFolderSpans(SpannableStringBuilder sb) { 215 for (Folder f : mFoldersSortedSet) { 216 addSpan(sb, f); 217 } 218 219 if (mFoldersSortedSet.isEmpty()) { 220 Folder addLabel = new Folder(); 221 final Resources r = mContext.getResources(); 222 addLabel.name = r.getString(R.string.add_label); 223 addLabel.bgColor = "" 224 + r.getColor(R.color.conv_header_add_label_background); 225 addLabel.fgColor = "" + r.getColor(R.color.conv_header_add_label_text); 226 addSpan(sb, addLabel); 227 } 228 } 229 230 private void addSpan(SpannableStringBuilder sb, Folder folder) { 231 final int start = sb.length(); 232 sb.append(folder.name); 233 final int end = sb.length(); 234 235 final int fgColor = folder.getForegroundColor(mDefaultFgColor); 236 final int bgColor = folder.getBackgroundColor(mDefaultBgColor); 237 238 sb.setSpan(new BackgroundColorSpan(bgColor), start, end, 239 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 240 sb.setSpan(new ForegroundColorSpan(fgColor), start, end, 241 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 242 sb.setSpan(new FolderSpan(sb, mDims), start, end, 243 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 244 } 245 246 } 247 248} 249