ConversationViewHeader.java revision 5ff63747a1b5c6e2197528972cbc3ba808b09d8d
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.view.ViewGroup;
31import android.widget.RelativeLayout;
32import android.widget.TextView;
33
34import com.android.mail.R;
35import com.android.mail.browse.FolderSpan.FolderSpanDimensions;
36import com.android.mail.providers.Conversation;
37import com.android.mail.providers.Folder;
38import com.android.mail.ui.FolderDisplayer;
39import com.android.mail.utils.Utils;
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 String mSubject;
74    private TextView mSubjectView;
75    private FolderSpanTextView mFoldersView;
76    private ConversationViewHeaderCallbacks mCallbacks;
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
90    @Override
91    protected void onFinishInflate() {
92        super.onFinishInflate();
93
94        mSubjectView = (TextView) findViewById(R.id.subject);
95        mFoldersView = (FolderSpanTextView) findViewById(R.id.folders);
96
97        mFoldersView.setOnClickListener(this);
98        mFolderDisplayer = new ConversationFolderDisplayer(getContext(), mFoldersView);
99    }
100
101    @Override
102    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
103        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
104
105        // reposition the folders if they don't fit horizontally next to the
106        // subject
107        // (taking into account child margins and parent padding)
108        final int childWidthSum = getTotalMeasuredChildWidth(mSubjectView)
109                + getTotalMeasuredChildWidth(mFoldersView) + getPaddingLeft() + getPaddingRight();
110
111        if (childWidthSum > getMeasuredWidth()) {
112            LayoutParams params = (LayoutParams) mFoldersView.getLayoutParams();
113            params.addRule(RelativeLayout.BELOW, R.id.subject);
114            params.addRule(RelativeLayout.ALIGN_BASELINE, 0);
115            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
116        }
117    }
118
119    private static int getTotalMeasuredChildWidth(View child) {
120        LayoutParams p = (LayoutParams) child.getLayoutParams();
121        return child.getMeasuredWidth() + p.leftMargin + p.rightMargin;
122    }
123
124    public void setCallbacks(ConversationViewHeaderCallbacks callbacks) {
125        mCallbacks = callbacks;
126    }
127
128    public void setSubject(final String subject, boolean notify) {
129        mSubject = subject;
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    public String getSubject() {
146        return mSubject;
147    }
148
149    public void setFolders(Conversation conv, boolean notify) {
150        SpannableStringBuilder sb = new SpannableStringBuilder();
151
152        // TODO: read 'show priority arrows' pref from settings
153        final boolean importanceArrowsEnabled = true;
154        // TODO: read importance value from Conversation
155        if (importanceArrowsEnabled && true /* conv.isImportant */) {
156            sb.append('.');
157            sb.setSpan(new PriorityIndicatorSpan(getContext(),
158                    R.drawable.ic_email_caret_none_important_unread, mFoldersView.getPadding(), 0),
159                    0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
160        }
161
162        mFolderDisplayer.loadConversationFolders(conv.rawFolders, null /* ignoreFolder */);
163        mFolderDisplayer.appendFolderSpans(sb);
164
165        mFoldersView.setText(sb);
166
167        if (notify) {
168            handleSizeChanged();
169        }
170    }
171
172    public int getPremeasuredHeight() {
173        ViewGroup parent = (ViewGroup) getParent();
174        return Utils.measureViewHeight(this, parent);
175    }
176
177    @Override
178    public void onClick(View v) {
179        if (R.id.folders == v.getId()) {
180            if (mCallbacks != null) {
181                mCallbacks.onFoldersClicked();
182            }
183        }
184    }
185
186    private void handleSizeChanged() {
187        mSizeChanged = true;
188    }
189
190    @Override
191    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
192        super.onSizeChanged(w, h, oldw, oldh);
193
194        if (mSizeChanged) {
195            // propagate new size to webview conversation header spacer
196            // only do this for known size changes
197            if (mCallbacks != null) {
198                mCallbacks.onConversationViewHeaderHeightChange(h);
199            }
200
201            mSizeChanged = false;
202        }
203    }
204
205    private static class ConversationFolderDisplayer extends FolderDisplayer {
206
207        private FolderSpanDimensions mDims;
208
209        public ConversationFolderDisplayer(Context context, FolderSpanDimensions dims) {
210            super(context);
211            mDims = dims;
212        }
213
214        public void appendFolderSpans(SpannableStringBuilder sb) {
215            for (Folder f : mFoldersSortedSet) {
216                addSpan(sb, f);
217            }
218
219            if (mFoldersSortedSet.isEmpty()) {
220                Folder addLabel = new Folder();
221                final Resources r = mContext.getResources();
222                addLabel.name = r.getString(R.string.add_label);
223                addLabel.bgColor = ""
224                        + r.getColor(R.color.conv_header_add_label_background);
225                addLabel.fgColor = "" + r.getColor(R.color.conv_header_add_label_text);
226                addSpan(sb, addLabel);
227            }
228        }
229
230        private void addSpan(SpannableStringBuilder sb, Folder folder) {
231            final int start = sb.length();
232            sb.append(folder.name);
233            final int end = sb.length();
234
235            final int fgColor = folder.getForegroundColor(mDefaultFgColor);
236            final int bgColor = folder.getBackgroundColor(mDefaultBgColor);
237
238            sb.setSpan(new BackgroundColorSpan(bgColor), start, end,
239                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
240            sb.setSpan(new ForegroundColorSpan(fgColor), start, end,
241                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
242            sb.setSpan(new FolderSpan(sb, mDims), start, end,
243                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
244        }
245
246    }
247
248}
249