ConversationViewHeader.java revision 0c3c2058f75487713c64634c7f08025fd78ba012
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.text.Spannable;
23import android.text.SpannableStringBuilder;
24import android.text.TextUtils;
25import android.text.style.BackgroundColorSpan;
26import android.text.style.ForegroundColorSpan;
27import android.util.AttributeSet;
28import android.view.View;
29import android.view.View.OnClickListener;
30import android.widget.RelativeLayout;
31import android.widget.TextView;
32
33import com.android.mail.R;
34import com.android.mail.browse.ConversationViewAdapter.ConversationAccountController;
35import com.android.mail.browse.FolderSpan.FolderSpanDimensions;
36import com.android.mail.providers.Conversation;
37import com.android.mail.providers.Folder;
38import com.android.mail.providers.Settings;
39import com.android.mail.ui.FolderDisplayer;
40
41/**
42 * A view for the subject and folders in the conversation view. This container
43 * makes an attempt to combine subject and folders on the same horizontal line if
44 * there is enough room to fit both without wrapping. If they overlap, it
45 * adjusts the layout to position the folders below the subject.
46 */
47public class ConversationViewHeader extends RelativeLayout implements OnClickListener {
48
49    public interface ConversationViewHeaderCallbacks {
50        /**
51         * Called in response to a click on the folders region.
52         */
53        void onFoldersClicked();
54
55        /**
56         * Called when the height of the {@link ConversationViewHeader} changes.
57         *
58         * @param newHeight the new height in px
59         */
60        void onConversationViewHeaderHeightChange(int newHeight);
61
62        /**
63         * Measure a subject string for display outside a conversation view and
64         * return the substring of trailing characters that didn't fit. Should
65         * not actually render the text, just measure it.
66         *
67         * @param subject string to measure
68         * @return the remainder of text that didn't fit
69         */
70        String getSubjectRemainder(String subject);
71    }
72
73    private TextView mSubjectView;
74    private FolderSpanTextView mFoldersView;
75    private ConversationViewHeaderCallbacks mCallbacks;
76    private ConversationAccountController mAccountController;
77    private ConversationFolderDisplayer mFolderDisplayer;
78
79    private boolean mSizeChanged;
80
81    public ConversationViewHeader(Context context) {
82        this(context, null);
83    }
84
85    public ConversationViewHeader(Context context, AttributeSet attrs) {
86        super(context, attrs);
87    }
88
89    @Override
90    protected void onFinishInflate() {
91        super.onFinishInflate();
92
93        mSubjectView = (TextView) findViewById(R.id.subject);
94        mFoldersView = (FolderSpanTextView) findViewById(R.id.folders);
95
96        mFoldersView.setOnClickListener(this);
97        mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView);
98    }
99
100    @Override
101    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
102        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
103
104        // reposition the folders if they don't fit horizontally next to the
105        // subject
106        // (taking into account child margins and parent padding)
107        final int childWidthSum = getTotalMeasuredChildWidth(mSubjectView)
108                + getTotalMeasuredChildWidth(mFoldersView) + getPaddingLeft() + getPaddingRight();
109
110        if (childWidthSum > getMeasuredWidth()) {
111            LayoutParams params = (LayoutParams) mFoldersView.getLayoutParams();
112            params.addRule(RelativeLayout.BELOW, R.id.subject);
113            params.addRule(RelativeLayout.ALIGN_BASELINE, 0);
114            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
115        }
116    }
117
118    private static int getTotalMeasuredChildWidth(View child) {
119        final LayoutParams p = (LayoutParams) child.getLayoutParams();
120        return child.getMeasuredWidth() + p.leftMargin + p.rightMargin;
121    }
122
123    public void setCallbacks(ConversationViewHeaderCallbacks callbacks,
124            ConversationAccountController accountController) {
125        mCallbacks = callbacks;
126        mAccountController = accountController;
127    }
128
129    public void setSubject(final String subject, boolean notify) {
130        String subjectToShow = subject;
131        if (mCallbacks != null && mCallbacks.getSubjectRemainder(subject) == null) {
132            subjectToShow = null;
133        }
134        mSubjectView.setText(subjectToShow);
135
136        if (TextUtils.isEmpty(subjectToShow)) {
137            mSubjectView.setVisibility(GONE);
138        }
139
140        if (notify) {
141            handleSizeChanged();
142        }
143    }
144
145
146    public void setFoldersVisible(boolean show) {
147        mFoldersView.setVisibility(show ? View.VISIBLE : View.GONE);
148    }
149
150    public void setFolders(Conversation conv, boolean notify) {
151        setFoldersVisible(true);
152        SpannableStringBuilder sb = new SpannableStringBuilder();
153        final Settings settings = mAccountController.getAccount().settings;
154        if (settings.priorityArrowsEnabled && conv.isImportant()) {
155            sb.append('.');
156            sb.setSpan(new PriorityIndicatorSpan(getContext(),
157                    R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0),
158                    0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
159        }
160
161        mFolderDisplayer.loadConversationFolders(conv, null /* ignoreFolder */);
162        mFolderDisplayer.appendFolderSpans(sb);
163
164        mFoldersView.setText(sb);
165
166        if (notify) {
167            handleSizeChanged();
168        }
169    }
170
171    /**
172     * Update the conversation view header to reflect the updated conversation.
173     */
174    public void onConversationUpdated(Conversation conv) {
175        // The only things we have to worry about when the conversation changes
176        // in the conversation header are the folders and priority indicators.
177        // Updating these will resize the space for the header.
178        setFolders(conv, false);
179    }
180
181    @Override
182    public void onClick(View v) {
183        if (R.id.folders == v.getId()) {
184            if (mCallbacks != null) {
185                mCallbacks.onFoldersClicked();
186            }
187        }
188    }
189
190    private void handleSizeChanged() {
191        mSizeChanged = true;
192    }
193
194    @Override
195    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
196        super.onSizeChanged(w, h, oldw, oldh);
197
198        if (mSizeChanged) {
199            // propagate new size to webview conversation header spacer
200            // only do this for known size changes
201            if (mCallbacks != null) {
202                mCallbacks.onConversationViewHeaderHeightChange(h);
203            }
204
205            mSizeChanged = false;
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 (Folder f : mFoldersSortedSet) {
220                addSpan(sb, f);
221            }
222
223            if (mFoldersSortedSet.isEmpty()) {
224                final Folder addLabel = Folder.newUnsafeInstance();
225                final Resources r = mContext.getResources();
226                addLabel.name = r.getString(R.string.add_label);
227                addLabel.bgColor = ""
228                        + r.getColor(R.color.conv_header_add_label_background);
229                addLabel.fgColor = "" + r.getColor(R.color.conv_header_add_label_text);
230                addSpan(sb, addLabel);
231            }
232        }
233
234        private void addSpan(SpannableStringBuilder sb, Folder folder) {
235            final int start = sb.length();
236            sb.append(folder.name);
237            final int end = sb.length();
238
239            final int fgColor = folder.getForegroundColor(mDefaultFgColor);
240            final int bgColor = folder.getBackgroundColor(mDefaultBgColor);
241
242            sb.setSpan(new BackgroundColorSpan(bgColor), start, end,
243                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
244            sb.setSpan(new ForegroundColorSpan(fgColor), start, end,
245                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
246            sb.setSpan(new FolderSpan(sb, mDims), start, end,
247                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
248        }
249
250    }
251}
252