ConversationViewHeader.java revision b53fe05fb867685d0d3f2f37daf0f93bc6488ded
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 private static final String LOG_TAG = LogTag.getLogTag(); 68 private TextView mSubjectView; 69 private FolderSpanTextView mFoldersView; 70 private ConversationViewHeaderCallbacks mCallbacks; 71 private ConversationAccountController mAccountController; 72 private ConversationFolderDisplayer mFolderDisplayer; 73 private ConversationHeaderItem mHeaderItem; 74 75 /** 76 * Instantiated from this layout: conversation_view_header.xml 77 * @param context 78 */ 79 public ConversationViewHeader(Context context) { 80 this(context, null); 81 } 82 83 public ConversationViewHeader(Context context, AttributeSet attrs) { 84 super(context, attrs); 85 } 86 87 @Override 88 protected void onFinishInflate() { 89 super.onFinishInflate(); 90 91 mSubjectView = (TextView) findViewById(R.id.subject); 92 mFoldersView = (FolderSpanTextView) findViewById(R.id.folders); 93 94 mFoldersView.setOnClickListener(this); 95 mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView); 96 } 97 98 @Override 99 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 100 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 101 102 // reposition the folders if they don't fit horizontally next to the 103 // subject 104 // (taking into account child margins and parent padding) 105 final int childWidthSum = getTotalMeasuredChildWidth(mSubjectView) 106 + getTotalMeasuredChildWidth(mFoldersView) + getPaddingLeft() + getPaddingRight(); 107 108 if (childWidthSum > getMeasuredWidth()) { 109 LayoutParams params = (LayoutParams) mFoldersView.getLayoutParams(); 110 params.addRule(RelativeLayout.BELOW, R.id.subject); 111 params.addRule(RelativeLayout.ALIGN_BASELINE, 0); 112 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 113 } 114 } 115 116 private static int getTotalMeasuredChildWidth(View child) { 117 final LayoutParams p = (LayoutParams) child.getLayoutParams(); 118 return child.getMeasuredWidth() + p.leftMargin + p.rightMargin; 119 } 120 121 public void setCallbacks(ConversationViewHeaderCallbacks callbacks, 122 ConversationAccountController accountController) { 123 mCallbacks = callbacks; 124 mAccountController = accountController; 125 } 126 127 public void setSubject(final String subject) { 128 mSubjectView.setText(subject); 129 if (TextUtils.isEmpty(subject)) { 130 mSubjectView.setVisibility(GONE); 131 } 132 } 133 134 public void setFoldersVisible(boolean show) { 135 mFoldersView.setVisibility(show ? View.VISIBLE : View.GONE); 136 } 137 138 public void setFolders(Conversation conv) { 139 setFoldersVisible(true); 140 SpannableStringBuilder sb = new SpannableStringBuilder(); 141 final Settings settings = mAccountController.getAccount().settings; 142 if (settings.priorityArrowsEnabled && conv.isImportant()) { 143 sb.append('.'); 144 sb.setSpan(new PriorityIndicatorSpan(getContext(), 145 R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0), 146 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE); 147 } 148 149 mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */, 150 -1 /* ignoreFolderType */); 151 mFolderDisplayer.appendFolderSpans(sb); 152 153 mFoldersView.setText(sb); 154 } 155 156 public void bind(ConversationHeaderItem headerItem) { 157 mHeaderItem = headerItem; 158 } 159 160 private int measureHeight() { 161 ViewGroup parent = (ViewGroup) getParent(); 162 if (parent == null) { 163 LogUtils.e(LOG_TAG, "Unable to measure height of conversation header"); 164 return getHeight(); 165 } 166 final int h = Utils.measureViewHeight(this, parent); 167 return h; 168 } 169 170 /** 171 * Update the conversation view header to reflect the updated conversation. 172 */ 173 public void onConversationUpdated(Conversation conv) { 174 // The only things we have to worry about when the conversation changes 175 // in the conversation header are the folders and priority indicators. 176 // Updating these will resize the space for the header. 177 setFolders(conv); 178 if (mHeaderItem != null) { 179 final int h = measureHeight(); 180 if (mHeaderItem.setHeight(h)) { 181 mCallbacks.onConversationViewHeaderHeightChange(h); 182 } 183 } 184 } 185 186 @Override 187 public void onClick(View v) { 188 if (R.id.folders == v.getId()) { 189 if (mCallbacks != null) { 190 mCallbacks.onFoldersClicked(); 191 } 192 } 193 } 194 195 private static class ConversationFolderDisplayer extends FolderDisplayer { 196 197 private FolderSpanDimensions mDims; 198 199 public ConversationFolderDisplayer(Context context, FolderSpanDimensions dims) { 200 super(context); 201 mDims = dims; 202 } 203 204 public void appendFolderSpans(SpannableStringBuilder sb) { 205 for (final Folder f : mFoldersSortedSet) { 206 final int bgColor = f.getBackgroundColor(mDefaultBgColor); 207 final int fgColor = f.getForegroundColor(mDefaultFgColor); 208 addSpan(sb, f.name, bgColor, fgColor); 209 } 210 211 if (mFoldersSortedSet.isEmpty()) { 212 final Resources r = mContext.getResources(); 213 final String name = r.getString(R.string.add_label); 214 final int bgColor = r.getColor(R.color.conv_header_add_label_background); 215 final int fgColor = r.getColor(R.color.conv_header_add_label_text); 216 addSpan(sb, name, bgColor, fgColor); 217 } 218 } 219 220 private void addSpan(SpannableStringBuilder sb, String name, int bgColor, 221 int fgColor) { 222 final int start = sb.length(); 223 sb.append(name); 224 final int end = sb.length(); 225 226 sb.setSpan(new BackgroundColorSpan(bgColor), start, end, 227 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 228 sb.setSpan(new ForegroundColorSpan(fgColor), start, end, 229 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 230 sb.setSpan(new FolderSpan(sb, mDims), start, end, 231 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 232 } 233 234 } 235} 236