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