ConversationViewHeader.java revision 93405645e9b637c89e1d2d3be4fe507179c4a854
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.os.Build;
23import android.text.Spannable;
24import android.text.SpannableStringBuilder;
25import android.text.TextUtils;
26import android.text.style.BackgroundColorSpan;
27import android.text.style.ForegroundColorSpan;
28import android.util.AttributeSet;
29import android.util.TypedValue;
30import android.view.View;
31import android.view.View.OnClickListener;
32import android.view.ViewGroup;
33import android.widget.LinearLayout;
34import android.widget.TextView;
35
36import com.android.mail.R;
37import com.android.mail.browse.ConversationViewAdapter.ConversationHeaderItem;
38import com.android.mail.browse.FolderSpan.FolderSpanDimensions;
39import com.android.mail.providers.Conversation;
40import com.android.mail.providers.Folder;
41import com.android.mail.providers.Settings;
42import com.android.mail.ui.FolderDisplayer;
43import com.android.mail.utils.LogTag;
44import com.android.mail.utils.LogUtils;
45import com.android.mail.utils.Utils;
46
47import java.util.Locale;
48
49/**
50 * A view for the subject and folders in the conversation view. This container
51 * makes an attempt to combine subject and folders on the same horizontal line if
52 * there is enough room to fit both without wrapping. If they overlap, it
53 * adjusts the layout to position the folders below the subject.
54 */
55public class ConversationViewHeader extends LinearLayout implements OnClickListener {
56
57    private final boolean mIsRtl;
58
59    public interface ConversationViewHeaderCallbacks {
60        /**
61         * Called in response to a click on the folders region.
62         */
63        void onFoldersClicked();
64
65        /**
66         * Called when the height of the {@link ConversationViewHeader} changes.
67         *
68         * @param newHeight the new height in px
69         */
70        void onConversationViewHeaderHeightChange(int newHeight);
71    }
72
73    private static final String LOG_TAG = LogTag.getLogTag();
74    private TextView mSubjectView;
75    private FolderSpanTextView mFoldersView;
76    private ConversationViewHeaderCallbacks mCallbacks;
77    private ConversationAccountController mAccountController;
78    private ConversationFolderDisplayer mFolderDisplayer;
79    private ConversationHeaderItem mHeaderItem;
80
81    private boolean mLargeText;
82    private final float mCondensedTextSize;
83    private final int mCondensedTopPadding;
84
85    /**
86     * Instantiated from this layout: conversation_view_header.xml
87     * @param context
88     */
89    public ConversationViewHeader(Context context) {
90        this(context, null);
91    }
92
93    public ConversationViewHeader(Context context, AttributeSet attrs) {
94        super(context, attrs);
95        mLargeText = true;
96        final Resources resources = getResources();
97        mCondensedTextSize =
98                resources.getDimensionPixelSize(R.dimen.conversation_header_font_size_condensed);
99        mCondensedTopPadding = resources.getDimensionPixelSize(
100                R.dimen.conversation_header_vertical_padding_condensed);
101        mIsRtl =
102                TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == LAYOUT_DIRECTION_RTL;
103    }
104
105    @Override
106    protected void onFinishInflate() {
107        super.onFinishInflate();
108
109        mSubjectView = (TextView) findViewById(R.id.subject);
110        mFoldersView = (FolderSpanTextView) findViewById(R.id.folders);
111
112        mFoldersView.setOnClickListener(this);
113        mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView, mIsRtl);
114    }
115
116    @Override
117    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
118        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
119
120        // If we currently have large text and we have greater than 2 lines,
121        // switch to smaller text size with smaller top padding and re-measure
122        if (mLargeText && mSubjectView.getLineCount() > 2) {
123            mSubjectView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mCondensedTextSize);
124
125            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
126                // start, top, end, bottom
127                mSubjectView.setPaddingRelative(mSubjectView.getPaddingStart(),
128                        mCondensedTopPadding, mSubjectView.getPaddingEnd(),
129                        mSubjectView.getPaddingBottom());
130            } else {
131                mSubjectView.setPadding(mSubjectView.getPaddingLeft(),
132                        mCondensedTopPadding, mSubjectView.getPaddingRight(),
133                        mSubjectView.getPaddingBottom());
134            }
135
136            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
137        }
138    }
139
140    public void setCallbacks(ConversationViewHeaderCallbacks callbacks,
141            ConversationAccountController accountController) {
142        mCallbacks = callbacks;
143        mAccountController = accountController;
144    }
145
146    public void setSubject(final String subject) {
147        if (TextUtils.isEmpty(subject)) {
148            mSubjectView.setText(R.string.no_subject);
149        } else {
150            mSubjectView.setText(subject);
151        }
152    }
153
154    public void setFoldersVisible(boolean show) {
155        mFoldersView.setVisibility(show ? View.VISIBLE : View.GONE);
156    }
157
158    public void setFolders(Conversation conv) {
159        setFoldersVisible(true);
160        SpannableStringBuilder sb = new SpannableStringBuilder();
161        final Settings settings = mAccountController.getAccount().settings;
162        if (settings.priorityArrowsEnabled && conv.isImportant()) {
163            sb.append('.');
164            sb.setSpan(new PriorityIndicatorSpan(getContext(),
165                    R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0,
166                    mFoldersView.getPaddingAbove(), mFoldersView.getPaddingBefore(), mIsRtl),
167                    0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
168        }
169
170        mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */,
171                -1 /* ignoreFolderType */);
172        mFolderDisplayer.appendFolderSpans(sb);
173
174        mFoldersView.setText(sb);
175    }
176
177    public void bind(ConversationHeaderItem headerItem) {
178        mHeaderItem = headerItem;
179    }
180
181    private int measureHeight() {
182        ViewGroup parent = (ViewGroup) getParent();
183        if (parent == null) {
184            LogUtils.e(LOG_TAG, "Unable to measure height of conversation header");
185            return getHeight();
186        }
187        final int h = Utils.measureViewHeight(this, parent);
188        return h;
189    }
190
191    /**
192     * Update the conversation view header to reflect the updated conversation.
193     */
194    public void onConversationUpdated(Conversation conv) {
195        // The only things we have to worry about when the conversation changes
196        // in the conversation header are the folders and priority indicators.
197        // Updating these will resize the space for the header.
198        setFolders(conv);
199        if (mHeaderItem != null) {
200            final int h = measureHeight();
201            if (mHeaderItem.setHeight(h)) {
202                mCallbacks.onConversationViewHeaderHeightChange(h);
203            }
204        }
205    }
206
207    @Override
208    public void onClick(View v) {
209        if (R.id.folders == v.getId()) {
210            if (mCallbacks != null) {
211                mCallbacks.onFoldersClicked();
212            }
213        }
214    }
215
216    private static class ConversationFolderDisplayer extends FolderDisplayer {
217
218        private final boolean mIsRtl;
219
220        private FolderSpanDimensions mDims;
221
222        public ConversationFolderDisplayer(
223                Context context, FolderSpanDimensions dims, boolean isRtl) {
224            super(context);
225            mDims = dims;
226            mIsRtl = isRtl;
227        }
228
229        public void appendFolderSpans(SpannableStringBuilder sb) {
230            for (final Folder f : mFoldersSortedSet) {
231                final int bgColor = f.getBackgroundColor(mDefaultBgColor);
232                final int fgColor = f.getForegroundColor(mDefaultFgColor);
233                addSpan(sb, f.name, bgColor, fgColor);
234            }
235
236            if (mFoldersSortedSet.isEmpty()) {
237                final Resources r = mContext.getResources();
238                final String name = r.getString(R.string.add_label);
239                final int bgColor = r.getColor(R.color.conv_header_add_label_background);
240                final int fgColor = r.getColor(R.color.conv_header_add_label_text);
241                addSpan(sb, name, bgColor, fgColor);
242            }
243        }
244
245        private void addSpan(SpannableStringBuilder sb, String name, int bgColor,
246                             int fgColor) {
247            final int start = sb.length();
248            sb.append(name);
249            final int end = sb.length();
250
251            sb.setSpan(new BackgroundColorSpan(bgColor), start, end,
252                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
253            sb.setSpan(new ForegroundColorSpan(fgColor), start, end,
254                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
255            sb.setSpan(new FolderSpan(sb, mDims, mIsRtl), start, end,
256                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
257        }
258
259    }
260}
261