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