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.graphics.Canvas; 23 import android.graphics.Paint; 24 import android.graphics.drawable.Drawable; 25 import android.support.v4.text.BidiFormatter; 26 import android.text.Layout; 27 import android.text.SpannableStringBuilder; 28 import android.text.Spanned; 29 import android.text.method.LinkMovementMethod; 30 import android.text.style.ReplacementSpan; 31 import android.util.AttributeSet; 32 import android.widget.TextView; 33 34 import com.android.mail.R; 35 import com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks; 36 import com.android.mail.providers.Account; 37 import com.android.mail.providers.Conversation; 38 import com.android.mail.providers.Folder; 39 import com.android.mail.providers.Settings; 40 import com.android.mail.text.ChangeLabelsSpan; 41 import com.android.mail.text.FolderSpan; 42 import com.android.mail.ui.FolderDisplayer; 43 44 /** 45 * A TextView that displays the conversation subject and list of folders for the message. 46 * The view knows the widest that any of its containing {@link com.android.mail.text.FolderSpan}s 47 * can be. They cannot exceed the TextView line width, or else {@link Layout} will split up the 48 * spans in strange places. 49 */ 50 public class SubjectAndFolderView extends TextView implements FolderSpan.FolderSpanDimensions { 51 private final String mNoFolderChipName; 52 private final int mNoFolderBgColor; 53 private final int mNoFolderFgColor; 54 private final Drawable mImportanceMarkerDrawable; 55 private final int mChipVerticalOffset; 56 57 private int mMaxSpanWidth; 58 59 private ConversationFolderDisplayer mFolderDisplayer; 60 61 private String mSubject; 62 63 private boolean mVisibleFolders; 64 65 private ConversationViewAdapter.ConversationHeaderItem mHeaderItem; 66 67 private BidiFormatter mBidiFormatter; 68 SubjectAndFolderView(Context context)69 public SubjectAndFolderView(Context context) { 70 this(context, null); 71 } 72 SubjectAndFolderView(Context context, AttributeSet attrs)73 public SubjectAndFolderView(Context context, AttributeSet attrs) { 74 super(context, attrs); 75 76 final Resources res = getResources(); 77 mNoFolderChipName = res.getString(R.string.add_label); 78 mNoFolderBgColor = res.getColor(R.color.conv_header_add_label_background); 79 mNoFolderFgColor = res.getColor(R.color.conv_header_add_label_text); 80 mImportanceMarkerDrawable = res.getDrawable( 81 R.drawable.ic_email_caret_none_important_unread); 82 mImportanceMarkerDrawable.setBounds(0, 0, mImportanceMarkerDrawable.getIntrinsicWidth(), 83 mImportanceMarkerDrawable.getIntrinsicHeight()); 84 mChipVerticalOffset = res.getDimensionPixelOffset(R.dimen.folder_cv_vertical_offset); 85 86 mVisibleFolders = false; 87 mFolderDisplayer = new ConversationFolderDisplayer(getContext()); 88 } 89 90 @Override onMeasure(int widthMeasureSpec, int heightMeasureSpec)91 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 92 mMaxSpanWidth = MeasureSpec.getSize(widthMeasureSpec) - getTotalPaddingLeft() 93 - getTotalPaddingRight(); 94 95 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 96 } 97 setSubject(String subject)98 public void setSubject(String subject) { 99 mSubject = Conversation.getSubjectForDisplay(getContext(), null /* badgeText */, subject); 100 101 if (!mVisibleFolders) { 102 setText(mSubject); 103 } 104 } 105 setFolders(ConversationViewHeaderCallbacks callbacks, Account account, Conversation conv)106 public void setFolders(ConversationViewHeaderCallbacks callbacks, Account account, 107 Conversation conv) { 108 mVisibleFolders = true; 109 final BidiFormatter bidiFormatter = getBidiFormatter(); 110 final String wrappedSubject = mSubject == null ? "" : bidiFormatter.unicodeWrap(mSubject); 111 final SpannableStringBuilder sb = new SpannableStringBuilder(wrappedSubject); 112 sb.append('\u0020'); 113 final Settings settings = account.settings; 114 final int start = sb.length(); 115 if (settings.importanceMarkersEnabled && conv.isImportant()) { 116 sb.append(".\u0020"); 117 sb.setSpan(new ReplacementSpan() { 118 @Override 119 public int getSize(Paint paint, CharSequence text, int start, int end, 120 Paint.FontMetricsInt fm) { 121 return mImportanceMarkerDrawable.getIntrinsicWidth(); 122 } 123 124 @Override 125 public void draw(Canvas canvas, CharSequence text, int start, int end, float x, 126 int top, int baseline, int bottom, Paint paint) { 127 canvas.save(); 128 final int transY = baseline + mChipVerticalOffset - 129 mImportanceMarkerDrawable.getIntrinsicHeight(); 130 canvas.translate(x, transY); 131 mImportanceMarkerDrawable.draw(canvas); 132 canvas.restore(); 133 } 134 }, start, start + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 135 } 136 137 mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */, 138 -1 /* ignoreFolderType */); 139 mFolderDisplayer.constructFolderChips(sb); 140 141 final int end = sb.length(); 142 sb.setSpan(new ChangeLabelsSpan(callbacks), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 143 144 setText(sb); 145 setMovementMethod(LinkMovementMethod.getInstance()); 146 } 147 bind(ConversationViewAdapter.ConversationHeaderItem headerItem)148 public void bind(ConversationViewAdapter.ConversationHeaderItem headerItem) { 149 mHeaderItem = headerItem; 150 } 151 getBidiFormatter()152 private BidiFormatter getBidiFormatter() { 153 if (mBidiFormatter == null) { 154 final ConversationViewAdapter adapter = mHeaderItem != null 155 ? mHeaderItem.getAdapter() : null; 156 if (adapter == null) { 157 mBidiFormatter = BidiFormatter.getInstance(); 158 } else { 159 mBidiFormatter = adapter.getBidiFormatter(); 160 } 161 } 162 return mBidiFormatter; 163 } 164 getSubject()165 public String getSubject() { 166 return mSubject; 167 } 168 169 @Override getMaxChipWidth()170 public int getMaxChipWidth() { 171 return mMaxSpanWidth; 172 } 173 174 private class ConversationFolderDisplayer extends FolderDisplayer { 175 ConversationFolderDisplayer(Context context)176 public ConversationFolderDisplayer(Context context) { 177 super(context); 178 } 179 180 @Override initializeDrawableResources()181 protected void initializeDrawableResources() { 182 super.initializeDrawableResources(); 183 final Resources res = mContext.getResources(); 184 mFolderDrawableResources.overflowGradientPadding = 0; // not applicable 185 mFolderDrawableResources.folderHorizontalPadding = 186 res.getDimensionPixelOffset(R.dimen.folder_cv_cell_content_padding); 187 mFolderDrawableResources.folderFontSize = 188 res.getDimensionPixelOffset(R.dimen.folder_cv_font_size); 189 mFolderDrawableResources.folderVerticalOffset = mChipVerticalOffset; 190 } 191 constructFolderChips(SpannableStringBuilder sb)192 private void constructFolderChips(SpannableStringBuilder sb) { 193 for (final Folder f : mFoldersSortedSet) { 194 addSpan(sb, f.name, f.getForegroundColor(mFolderDrawableResources.defaultFgColor), 195 f.getBackgroundColor(mFolderDrawableResources.defaultBgColor)); 196 } 197 198 if (mFoldersSortedSet.isEmpty()) { 199 addSpan(sb, mNoFolderChipName, mNoFolderFgColor, mNoFolderBgColor); 200 } 201 } 202 addSpan(SpannableStringBuilder sb, String name, int fgColor, int bgColor)203 private void addSpan(SpannableStringBuilder sb, String name, int fgColor, int bgColor) { 204 final FolderSpan span = new FolderSpan(name, fgColor, bgColor, mFolderDrawableResources, 205 getBidiFormatter(), SubjectAndFolderView.this); 206 final int start = sb.length(); 207 sb.append(name); 208 sb.setSpan(span, start, start + name.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 209 sb.append(" "); 210 } 211 } 212 } 213