1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package com.android.messaging.ui;
17
18import android.content.Context;
19import android.content.Intent;
20import android.support.v4.text.BidiFormatter;
21import android.support.v4.text.TextDirectionHeuristicsCompat;
22import android.text.TextUtils;
23import android.util.AttributeSet;
24import android.view.LayoutInflater;
25import android.view.View;
26import android.view.View.OnLayoutChangeListener;
27import android.widget.LinearLayout;
28import android.widget.TextView;
29
30import com.android.messaging.R;
31import com.android.messaging.datamodel.binding.BindingBase;
32import com.android.messaging.datamodel.binding.DetachableBinding;
33import com.android.messaging.datamodel.data.PersonItemData;
34import com.android.messaging.datamodel.data.PersonItemData.PersonItemDataListener;
35import com.android.messaging.util.AccessibilityUtil;
36import com.android.messaging.util.UiUtils;
37
38/**
39 * Shows a view for a "person" - could be a contact or a participant. This always shows a
40 * contact icon on the left, and the person's display name on the right.
41 *
42 * This view is always bound to an abstract PersonItemData class, so to use it for a specific
43 * scenario, all you need to do is to create a concrete PersonItemData subclass that bridges
44 * between the underlying data (e.g. ParticipantData) and what the UI wants (e.g. display name).
45 */
46public class PersonItemView extends LinearLayout implements PersonItemDataListener,
47        OnLayoutChangeListener {
48    public interface PersonItemViewListener {
49        void onPersonClicked(PersonItemData data);
50        boolean onPersonLongClicked(PersonItemData data);
51    }
52
53    protected final DetachableBinding<PersonItemData> mBinding;
54    private TextView mNameTextView;
55    private TextView mDetailsTextView;
56    private ContactIconView mContactIconView;
57    private View mDetailsContainer;
58    private PersonItemViewListener mListener;
59    private boolean mAvatarOnly;
60
61    public PersonItemView(final Context context, final AttributeSet attrs) {
62        super(context, attrs);
63        mBinding = BindingBase.createDetachableBinding(this);
64        LayoutInflater.from(getContext()).inflate(R.layout.person_item_view, this, true);
65    }
66
67    @Override
68    protected void onFinishInflate() {
69        mNameTextView = (TextView) findViewById(R.id.name);
70        mDetailsTextView = (TextView) findViewById(R.id.details);
71        mContactIconView = (ContactIconView) findViewById(R.id.contact_icon);
72        mDetailsContainer = findViewById(R.id.details_container);
73        mNameTextView.addOnLayoutChangeListener(this);
74    }
75
76    @Override
77    protected void onDetachedFromWindow() {
78        super.onDetachedFromWindow();
79        if (mBinding.isBound()) {
80            mBinding.detach();
81        }
82    }
83
84    @Override
85    protected void onAttachedToWindow() {
86        super.onAttachedToWindow();
87        mBinding.reAttachIfPossible();
88    }
89
90    /**
91     * Binds to a person item data which will provide us info to be displayed.
92     * @param personData the PersonItemData to be bound to.
93     */
94    public void bind(final PersonItemData personData) {
95        if (mBinding.isBound()) {
96            if (mBinding.getData().equals(personData)) {
97                // Don't rebind if we are requesting the same data.
98                return;
99            }
100            mBinding.unbind();
101        }
102
103        if (personData != null) {
104            mBinding.bind(personData);
105            mBinding.getData().setListener(this);
106
107            // Accessibility reason : in case phone numbers are mixed in the display name,
108            // we need to vocalize it for talkback.
109            final String vocalizedDisplayName = AccessibilityUtil.getVocalizedPhoneNumber(
110                    getResources(), getDisplayName());
111            mNameTextView.setContentDescription(vocalizedDisplayName);
112        }
113        updateViewAppearance();
114    }
115
116    /**
117     * @return Display name, possibly comma-ellipsized.
118     */
119    private String getDisplayName() {
120        final int width = mNameTextView.getMeasuredWidth();
121        final String displayName = mBinding.getData().getDisplayName();
122        if (width == 0 || TextUtils.isEmpty(displayName) || !displayName.contains(",")) {
123            return displayName;
124        }
125        final String plusOneString = getContext().getString(R.string.plus_one);
126        final String plusNString = getContext().getString(R.string.plus_n);
127        return BidiFormatter.getInstance().unicodeWrap(
128                UiUtils.commaEllipsize(
129                        displayName,
130                        mNameTextView.getPaint(),
131                        width,
132                        plusOneString,
133                        plusNString).toString(),
134                TextDirectionHeuristicsCompat.LTR);
135    }
136
137    @Override
138    public void onLayoutChange(final View v, final int left, final int top, final int right,
139            final int bottom, final int oldLeft, final int oldTop, final int oldRight,
140            final int oldBottom) {
141        if (mBinding.isBound() && v == mNameTextView) {
142            setNameTextView();
143        }
144    }
145
146    /**
147     * When set to true, we display only the avatar of the person and hide everything else.
148     */
149    public void setAvatarOnly(final boolean avatarOnly) {
150        mAvatarOnly = avatarOnly;
151        mDetailsContainer.setVisibility(avatarOnly ? GONE : VISIBLE);
152    }
153
154    public boolean isAvatarOnly() {
155        return mAvatarOnly;
156    }
157
158    public void setNameTextColor(final int color) {
159        mNameTextView.setTextColor(color);
160    }
161
162    public void setDetailsTextColor(final int color) {
163        mDetailsTextView.setTextColor(color);
164    }
165
166    public void setListener(final PersonItemViewListener listener) {
167        mListener = listener;
168        if (mListener == null) {
169            return;
170        }
171        setOnClickListener(new OnClickListener() {
172            @Override
173            public void onClick(final View v) {
174                if (mListener != null && mBinding.isBound()) {
175                    mListener.onPersonClicked(mBinding.getData());
176                }
177            }
178        });
179        final OnLongClickListener onLongClickListener = new OnLongClickListener() {
180            @Override
181            public boolean onLongClick(View v) {
182                if (mListener != null && mBinding.isBound()) {
183                    return mListener.onPersonLongClicked(mBinding.getData());
184                }
185                return false;
186            }
187        };
188        setOnLongClickListener(onLongClickListener);
189        mContactIconView.setOnLongClickListener(onLongClickListener);
190    }
191
192    public void performClickOnAvatar() {
193        mContactIconView.performClick();
194    }
195
196    protected void updateViewAppearance() {
197        if (mBinding.isBound()) {
198            setNameTextView();
199
200            final String details = mBinding.getData().getDetails();
201            if (TextUtils.isEmpty(details)) {
202                mDetailsTextView.setVisibility(GONE);
203            } else {
204                mDetailsTextView.setVisibility(VISIBLE);
205                mDetailsTextView.setText(details);
206            }
207
208            mContactIconView.setImageResourceUri(mBinding.getData().getAvatarUri(),
209                    mBinding.getData().getContactId(), mBinding.getData().getLookupKey(),
210                    mBinding.getData().getNormalizedDestination());
211        } else {
212            mNameTextView.setText("");
213            mContactIconView.setImageResourceUri(null);
214        }
215    }
216
217    private void setNameTextView() {
218        final String displayName = getDisplayName();
219        if (TextUtils.isEmpty(displayName)) {
220            mNameTextView.setVisibility(GONE);
221        } else {
222            mNameTextView.setVisibility(VISIBLE);
223            mNameTextView.setText(displayName);
224        }
225    }
226
227    @Override
228    public void onPersonDataUpdated(final PersonItemData data) {
229        mBinding.ensureBound(data);
230        updateViewAppearance();
231    }
232
233    @Override
234    public void onPersonDataFailed(final PersonItemData data, final Exception exception) {
235        mBinding.ensureBound(data);
236        updateViewAppearance();
237    }
238
239    public Intent getClickIntent() {
240        return mBinding.getData().getClickIntent();
241    }
242}
243