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.drawable.Drawable; 23 import android.support.v4.text.BidiFormatter; 24 import android.text.Layout; 25 import android.text.Spannable; 26 import android.text.SpannableStringBuilder; 27 import android.text.Spanned; 28 import android.text.method.LinkMovementMethod; 29 import android.text.style.BackgroundColorSpan; 30 import android.text.style.DynamicDrawableSpan; 31 import android.text.style.ForegroundColorSpan; 32 import android.util.AttributeSet; 33 import android.widget.TextView; 34 35 import com.android.mail.R; 36 import com.android.mail.browse.ConversationViewHeader.ConversationViewHeaderCallbacks; 37 import com.android.mail.providers.Account; 38 import com.android.mail.providers.Conversation; 39 import com.android.mail.providers.Folder; 40 import com.android.mail.providers.Settings; 41 import com.android.mail.text.ChangeLabelsSpan; 42 import com.android.mail.ui.FolderDisplayer; 43 import com.android.mail.utils.ViewUtils; 44 45 /** 46 * A TextView that displays the conversation subject and list of folders for the message. 47 * The view knows the widest that any of its containing {@link FolderSpan}s can be. 48 * They cannot exceed the TextView line width, or else {@link Layout} 49 * will split up the spans in strange places. 50 */ 51 public class SubjectAndFolderView extends TextView 52 implements FolderSpan.FolderSpanDimensions { 53 54 private final int mFolderPadding; 55 private final int mFolderPaddingExtraWidth; 56 private final int mFolderPaddingAfter; 57 private final int mRoundedCornerRadius; 58 private final float mFolderSpanTextSize; 59 private final int mFolderMarginTop; 60 61 private int mMaxSpanWidth; 62 63 private ConversationFolderDisplayer mFolderDisplayer; 64 65 private String mSubject; 66 67 private boolean mVisibleFolders; 68 69 private ConversationViewAdapter.ConversationHeaderItem mHeaderItem; 70 71 private BidiFormatter mBidiFormatter; 72 73 public SubjectAndFolderView(Context context) { 74 this(context, null); 75 } 76 77 public SubjectAndFolderView(Context context, AttributeSet attrs) { 78 super(context, attrs); 79 80 mVisibleFolders = false; 81 mFolderDisplayer = new ConversationFolderDisplayer(getContext(), this); 82 83 final Resources r = getResources(); 84 mFolderPadding = r.getDimensionPixelOffset(R.dimen.conversation_folder_padding); 85 mFolderPaddingExtraWidth = r.getDimensionPixelOffset( 86 R.dimen.conversation_folder_padding_extra_width); 87 mFolderPaddingAfter = r.getDimensionPixelOffset( 88 R.dimen.conversation_folder_padding_after); 89 mRoundedCornerRadius = r.getDimensionPixelOffset( 90 R.dimen.folder_rounded_corner_radius); 91 mFolderSpanTextSize = r.getDimension(R.dimen.conversation_folder_font_size); 92 mFolderMarginTop = r.getDimensionPixelOffset(R.dimen.conversation_folder_margin_top); 93 } 94 95 @Override 96 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 97 mMaxSpanWidth = MeasureSpec.getSize(widthMeasureSpec) - getTotalPaddingLeft() 98 - getTotalPaddingRight(); 99 100 super.onMeasure(widthMeasureSpec, heightMeasureSpec); 101 } 102 103 @Override 104 public int getPadding() { 105 return mFolderPadding; 106 } 107 108 @Override 109 public int getPaddingExtraWidth() { 110 return mFolderPaddingExtraWidth; 111 } 112 113 @Override 114 public int getPaddingAfter() { 115 return mFolderPaddingAfter; 116 } 117 118 @Override 119 public int getMaxWidth() { 120 return mMaxSpanWidth; 121 } 122 123 @Override 124 public float getRoundedCornerRadius() { 125 return mRoundedCornerRadius; 126 } 127 128 @Override 129 public float getFolderSpanTextSize() { 130 return mFolderSpanTextSize; 131 } 132 133 @Override 134 public int getMarginTop() { 135 return mFolderMarginTop; 136 } 137 138 @Override 139 public boolean isRtl() { 140 return ViewUtils.isViewRtl(this); 141 } 142 143 public void setSubject(String subject) { 144 mSubject = Conversation.getSubjectForDisplay(getContext(), null /* badgeText */, subject); 145 146 if (!mVisibleFolders) { 147 setText(mSubject); 148 } 149 } 150 151 public void setFolders( 152 ConversationViewHeaderCallbacks callbacks, Account account, Conversation conv) { 153 mVisibleFolders = true; 154 final BidiFormatter bidiFormatter = getBidiFormatter(); 155 final SpannableStringBuilder sb = 156 new SpannableStringBuilder(bidiFormatter.unicodeWrap(mSubject)); 157 sb.append('\u0020'); 158 final Settings settings = account.settings; 159 final int start = sb.length(); 160 if (settings.importanceMarkersEnabled && conv.isImportant()) { 161 sb.append(".\u0020"); 162 sb.setSpan(new DynamicDrawableSpan(DynamicDrawableSpan.ALIGN_BASELINE) { 163 @Override 164 public Drawable getDrawable() { 165 Drawable d = getContext().getResources().getDrawable( 166 R.drawable.ic_email_caret_none_important_unread); 167 d.setBounds(0, 0, d.getIntrinsicWidth(), d.getIntrinsicHeight()); 168 return d; 169 } 170 }, 171 start, start + 1, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); 172 } 173 174 mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */, 175 -1 /* ignoreFolderType */); 176 mFolderDisplayer.appendFolderSpans(sb, bidiFormatter); 177 178 final int end = sb.length(); 179 sb.setSpan(new ChangeLabelsSpan(callbacks), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 180 181 setText(sb); 182 setMovementMethod(LinkMovementMethod.getInstance()); 183 } 184 185 public void bind(ConversationViewAdapter.ConversationHeaderItem headerItem) { 186 mHeaderItem = headerItem; 187 } 188 189 private BidiFormatter getBidiFormatter() { 190 if (mBidiFormatter == null) { 191 final ConversationViewAdapter adapter = mHeaderItem != null 192 ? mHeaderItem.getAdapter() : null; 193 if (adapter == null) { 194 mBidiFormatter = BidiFormatter.getInstance(); 195 } else { 196 mBidiFormatter = adapter.getBidiFormatter(); 197 } 198 } 199 return mBidiFormatter; 200 } 201 202 private static class ConversationFolderDisplayer extends FolderDisplayer { 203 204 private final FolderSpan.FolderSpanDimensions mDims; 205 206 public ConversationFolderDisplayer(Context context, FolderSpan.FolderSpanDimensions dims) { 207 super(context); 208 mDims = dims; 209 } 210 211 public void appendFolderSpans(SpannableStringBuilder sb, BidiFormatter bidiFormatter) { 212 for (final Folder f : mFoldersSortedSet) { 213 final int bgColor = f.getBackgroundColor(mDefaultBgColor); 214 final int fgColor = f.getForegroundColor(mDefaultFgColor); 215 addSpan(sb, f.name, bgColor, fgColor, bidiFormatter); 216 } 217 218 if (mFoldersSortedSet.isEmpty()) { 219 final Resources r = mContext.getResources(); 220 final String name = r.getString(R.string.add_label); 221 final int bgColor = r.getColor(R.color.conv_header_add_label_background); 222 final int fgColor = r.getColor(R.color.conv_header_add_label_text); 223 addSpan(sb, name, bgColor, fgColor, bidiFormatter); 224 } 225 } 226 227 private void addSpan(SpannableStringBuilder sb, String name, 228 int bgColor, int fgColor, BidiFormatter bidiFormatter) { 229 final int start = sb.length(); 230 sb.append(bidiFormatter.unicodeWrap(name)); 231 final int end = sb.length(); 232 233 sb.setSpan(new BackgroundColorSpan(bgColor), start, end, 234 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 235 sb.setSpan(new ForegroundColorSpan(fgColor), start, end, 236 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 237 sb.setSpan(new FolderSpan(sb, mDims), start, end, 238 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); 239 } 240 } 241 } 242