blob: 68832ffed19433f2147fbba6d301ff7ed3a313c6 [file] [log] [blame]
Daniel Lehmann392ccec2010-11-01 20:50:03 -07001/*
2 * Copyright (C) 2010 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 */
16
Dmitri Plotnikov18ffaa22010-12-03 14:28:00 -080017package com.android.contacts.editor;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070018
Daniel Lehmann392ccec2010-11-01 20:50:03 -070019import android.app.AlertDialog;
20import android.app.Dialog;
21import android.content.Context;
22import android.content.DialogInterface;
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -070023import android.content.DialogInterface.OnShowListener;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070024import android.os.Bundle;
Daniel Lehmann79c9c5a2010-11-11 16:05:27 -080025import android.os.Handler;
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -070026import android.text.Editable;
Dmitri Plotnikov11bb73b2011-02-25 20:39:58 -080027import android.text.TextUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070028import android.text.TextWatcher;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070029import android.util.AttributeSet;
Brian Attwell043fba62014-10-30 11:11:56 -070030import android.util.TypedValue;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -080031import android.view.LayoutInflater;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070032import android.view.View;
33import android.view.ViewGroup;
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -070034import android.view.WindowManager;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070035import android.view.inputmethod.EditorInfo;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -080036import android.widget.AdapterView;
37import android.widget.AdapterView.OnItemSelectedListener;
38import android.widget.ArrayAdapter;
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -070039import android.widget.Button;
Gary Mai186d7912016-08-10 14:41:45 -070040import android.widget.CheckedTextView;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070041import android.widget.EditText;
Katherine Kuan12a93632011-05-05 13:38:01 -070042import android.widget.ImageView;
Katherine Kuan63ffb902011-04-26 19:53:40 -070043import android.widget.LinearLayout;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -080044import android.widget.Spinner;
45import android.widget.TextView;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070046
Gary Mai0a49afa2016-12-05 15:53:58 -080047import com.android.contacts.ContactsUtils;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070048import com.android.contacts.R;
Gary Mai69c182a2016-12-05 13:07:03 -080049import com.android.contacts.model.RawContactDelta;
50import com.android.contacts.model.RawContactModifier;
51import com.android.contacts.model.ValuesDelta;
52import com.android.contacts.model.account.AccountType.EditType;
53import com.android.contacts.model.dataitem.DataKind;
Chiao Chenge0b2f1e2012-06-12 13:07:56 -070054import com.android.contacts.util.DialogManager;
55import com.android.contacts.util.DialogManager.DialogShowingView;
56
Dmitri Plotnikov14918c02010-12-07 17:20:27 -080057import java.util.List;
58
Daniel Lehmann392ccec2010-11-01 20:50:03 -070059/**
Katherine Kuan63ffb902011-04-26 19:53:40 -070060 * Base class for editors that handles labels and values. Uses
Maurice Chu851222a2012-06-21 11:43:08 -070061 * {@link ValuesDelta} to read any existing {@link RawContact} values, and to
Katherine Kuan63ffb902011-04-26 19:53:40 -070062 * correctly write any changes values.
Daniel Lehmann392ccec2010-11-01 20:50:03 -070063 */
Katherine Kuan63ffb902011-04-26 19:53:40 -070064public abstract class LabeledEditorView extends LinearLayout implements Editor, DialogShowingView {
Daniel Lehmann392ccec2010-11-01 20:50:03 -070065 protected static final String DIALOG_ID_KEY = "dialog_id";
66 private static final int DIALOG_ID_CUSTOM = 1;
67
68 private static final int INPUT_TYPE_CUSTOM = EditorInfo.TYPE_CLASS_TEXT
69 | EditorInfo.TYPE_TEXT_FLAG_CAP_WORDS;
70
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -080071 private Spinner mLabel;
72 private EditTypeAdapter mEditTypeAdapter;
Katherine Kuan63ffb902011-04-26 19:53:40 -070073 private View mDeleteContainer;
Katherine Kuan12a93632011-05-05 13:38:01 -070074 private ImageView mDelete;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070075
76 private DataKind mKind;
77 private ValuesDelta mEntry;
Maurice Chu851222a2012-06-21 11:43:08 -070078 private RawContactDelta mState;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070079 private boolean mReadOnly;
Katherine Kuan25914362011-04-29 19:42:01 -070080 private boolean mWasEmpty = true;
Katherine Kuan5e330ed2011-07-15 15:06:06 -070081 private boolean mIsDeletable = true;
Katherine Kuan2293e552011-07-21 20:25:44 -070082 private boolean mIsAttachedToWindow;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070083
84 private EditType mType;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070085
86 private ViewIdGenerator mViewIdGenerator;
87 private DialogManager mDialogManager = null;
88 private EditorListener mListener;
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -080089 protected int mMinLineItemHeight;
Gary Mai186d7912016-08-10 14:41:45 -070090 private int mSelectedLabelIndex;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070091
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -080092 /**
93 * A marker in the spinner adapter of the currently selected custom type.
94 */
95 public static final EditType CUSTOM_SELECTION = new EditType(0, 0);
96
97 private OnItemSelectedListener mSpinnerListener = new OnItemSelectedListener() {
98
99 @Override
100 public void onItemSelected(
101 AdapterView<?> parent, View view, int position, long id) {
102 onTypeSelectionChange(position);
103 }
104
105 @Override
106 public void onNothingSelected(AdapterView<?> parent) {
107 }
108 };
109
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700110 public LabeledEditorView(Context context) {
111 super(context);
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -0800112 init(context);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700113 }
114
115 public LabeledEditorView(Context context, AttributeSet attrs) {
116 super(context, attrs);
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -0800117 init(context);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700118 }
119
120 public LabeledEditorView(Context context, AttributeSet attrs, int defStyle) {
121 super(context, attrs, defStyle);
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -0800122 init(context);
123 }
124
Walter Jangd35e5ef2015-02-24 09:18:16 -0800125 public Long getRawContactId() {
126 return mState == null ? null : mState.getRawContactId();
127 }
128
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -0800129 private void init(Context context) {
130 mMinLineItemHeight = context.getResources().getDimensionPixelSize(
131 R.dimen.editor_min_line_item_height);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700132 }
133
Katherine Kuan63ffb902011-04-26 19:53:40 -0700134 /** {@inheritDoc} */
135 @Override
136 protected void onFinishInflate() {
137
Katherine Kuan63ffb902011-04-26 19:53:40 -0700138 mLabel = (Spinner) findViewById(R.id.spinner);
Daniel Lehmann67713b32012-02-21 21:57:09 -0800139 // Turn off the Spinner's own state management. We do this ourselves on rotation
140 mLabel.setId(View.NO_ID);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700141 mLabel.setOnItemSelectedListener(mSpinnerListener);
Wenyi Wang843de772015-11-10 10:24:04 -0800142 ViewSelectedFilter.suppressViewSelectedEvent(mLabel);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700143
Katherine Kuan12a93632011-05-05 13:38:01 -0700144 mDelete = (ImageView) findViewById(R.id.delete_button);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700145 mDeleteContainer = findViewById(R.id.delete_button_container);
Katherine Kuan12a93632011-05-05 13:38:01 -0700146 mDeleteContainer.setOnClickListener(new OnClickListener() {
Katherine Kuan63ffb902011-04-26 19:53:40 -0700147 @Override
148 public void onClick(View v) {
149 // defer removal of this button so that the pressed state is visible shortly
150 new Handler().post(new Runnable() {
151 @Override
152 public void run() {
Katherine Kuan2293e552011-07-21 20:25:44 -0700153 // Don't do anything if the view is no longer attached to the window
154 // (This check is needed because when this {@link Runnable} is executed,
155 // we can't guarantee the view is still valid.
156 if (!mIsAttachedToWindow) {
157 return;
158 }
159 // Send the delete request to the listener (which will in turn call
160 // deleteEditor() on this view if the deletion is valid - i.e. this is not
161 // the last {@link Editor} in the section).
Katherine Kuan63ffb902011-04-26 19:53:40 -0700162 if (mListener != null) {
Katherine Kuan2293e552011-07-21 20:25:44 -0700163 mListener.onDeleteRequested(LabeledEditorView.this);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700164 }
165 }
166 });
167 }
168 });
Brian Attwell043fba62014-10-30 11:11:56 -0700169
170 setPadding(getPaddingLeft(), getPaddingTop(), getPaddingRight(),
171 (int) getResources().getDimension(R.dimen.editor_padding_between_editor_views));
Katherine Kuan63ffb902011-04-26 19:53:40 -0700172 }
173
Katherine Kuan2293e552011-07-21 20:25:44 -0700174 @Override
175 protected void onAttachedToWindow() {
176 super.onAttachedToWindow();
177 // Keep track of when the view is attached or detached from the window, so we know it's
178 // safe to remove views (in case the user requests to delete this editor).
179 mIsAttachedToWindow = true;
180 }
181
182 @Override
183 protected void onDetachedFromWindow() {
184 super.onDetachedFromWindow();
185 mIsAttachedToWindow = false;
186 }
187
188 @Override
Walter Jang23a34d12015-07-08 09:41:05 -0700189 public void markDeleted() {
Katherine Kuan2293e552011-07-21 20:25:44 -0700190 // Keep around in model, but mark as deleted
191 mEntry.markDeleted();
Walter Jang23a34d12015-07-08 09:41:05 -0700192 }
193
194 @Override
195 public void deleteEditor() {
196 markDeleted();
Katherine Kuan2293e552011-07-21 20:25:44 -0700197
198 // Remove the view
Daniel Lehmannca87e9c2012-03-06 14:02:31 -0800199 EditorAnimator.getInstance().removeEditorView(this);
Katherine Kuan2293e552011-07-21 20:25:44 -0700200 }
201
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800202 public boolean isReadOnly() {
203 return mReadOnly;
204 }
205
Dmitri Plotnikov91d8e892010-12-07 20:36:51 -0800206 public int getBaseline(int row) {
207 if (row == 0 && mLabel != null) {
208 return mLabel.getBaseline();
209 }
210 return -1;
211 }
212
213 /**
Katherine Kuana94859f2011-07-20 13:38:27 -0700214 * Configures the visibility of the type label button and enables or disables it properly.
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700215 */
216 private void setupLabelButton(boolean shouldExist) {
Katherine Kuan63ffb902011-04-26 19:53:40 -0700217 if (shouldExist) {
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800218 mLabel.setEnabled(!mReadOnly && isEnabled());
Katherine Kuan63ffb902011-04-26 19:53:40 -0700219 mLabel.setVisibility(View.VISIBLE);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700220 } else {
221 mLabel.setVisibility(View.GONE);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700222 }
223 }
224
225 /**
Katherine Kuana94859f2011-07-20 13:38:27 -0700226 * Configures the visibility of the "delete" button and enables or disables it properly.
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700227 */
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700228 private void setupDeleteButton() {
229 if (mIsDeletable) {
Katherine Kuan63ffb902011-04-26 19:53:40 -0700230 mDeleteContainer.setVisibility(View.VISIBLE);
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800231 mDelete.setEnabled(!mReadOnly && isEnabled());
Katherine Kuan63ffb902011-04-26 19:53:40 -0700232 } else {
233 mDeleteContainer.setVisibility(View.GONE);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700234 }
235 }
236
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700237 public void setDeleteButtonVisible(boolean visible) {
238 if (mIsDeletable) {
Katherine Kuana94859f2011-07-20 13:38:27 -0700239 mDeleteContainer.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700240 }
241 }
242
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700243 protected void onOptionalFieldVisibilityChange() {
244 if (mListener != null) {
245 mListener.onRequest(EditorListener.EDITOR_FORM_CHANGED);
246 }
247 }
248
249 @Override
250 public void setEditorListener(EditorListener listener) {
251 mListener = listener;
252 }
253
Brian Attwelld690dff2014-12-02 15:04:56 -0800254 protected EditorListener getEditorListener(){
255 return mListener;
256 }
257
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700258 @Override
259 public void setDeletable(boolean deletable) {
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700260 mIsDeletable = deletable;
261 setupDeleteButton();
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700262 }
263
264 @Override
265 public void setEnabled(boolean enabled) {
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800266 super.setEnabled(enabled);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700267 mLabel.setEnabled(!mReadOnly && enabled);
268 mDelete.setEnabled(!mReadOnly && enabled);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700269 }
270
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800271 public Spinner getLabel() {
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700272 return mLabel;
273 }
274
Katherine Kuan12a93632011-05-05 13:38:01 -0700275 public ImageView getDelete() {
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700276 return mDelete;
277 }
278
279 protected DataKind getKind() {
280 return mKind;
281 }
282
283 protected ValuesDelta getEntry() {
284 return mEntry;
285 }
286
287 protected EditType getType() {
288 return mType;
289 }
290
291 /**
292 * Build the current label state based on selected {@link EditType} and
293 * possible custom label string.
294 */
Brian Attwelld690dff2014-12-02 15:04:56 -0800295 public void rebuildLabel() {
Brian Attwellf1402272014-12-16 16:00:08 -0800296 mEditTypeAdapter = new EditTypeAdapter(getContext());
Gary Mai186d7912016-08-10 14:41:45 -0700297 mEditTypeAdapter.setSelectedIndex(mSelectedLabelIndex);
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800298 mLabel.setAdapter(mEditTypeAdapter);
299 if (mEditTypeAdapter.hasCustomSelection()) {
300 mLabel.setSelection(mEditTypeAdapter.getPosition(CUSTOM_SELECTION));
Wenyi Wangdaf8a412015-11-06 12:59:57 -0800301 mDeleteContainer.setContentDescription(
302 getContext().getString(R.string.editor_delete_view_description,
303 mEntry.getAsString(mType.customColumn),
304 getContext().getString(mKind.titleRes)));
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800305 } else {
Walter Jang155299e2016-08-25 10:38:52 -0700306 if (mType != null && mType.labelRes > 0 && mKind.titleRes > 0) {
Tingting Wangcfa628c2016-07-13 17:31:06 -0700307 mLabel.setSelection(mEditTypeAdapter.getPosition(mType));
Wenyi Wangdaf8a412015-11-06 12:59:57 -0800308 mDeleteContainer.setContentDescription(
309 getContext().getString(R.string.editor_delete_view_description,
310 getContext().getString(mType.labelRes),
311 getContext().getString(mKind.titleRes)));
Walter Jang155299e2016-08-25 10:38:52 -0700312 } else if (mKind.titleRes > 0) {
Wenyi Wangdaf8a412015-11-06 12:59:57 -0800313 mDeleteContainer.setContentDescription(
314 getContext().getString(R.string.editor_delete_view_description_short,
315 getContext().getString(mKind.titleRes)));
316 }
317
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700318 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700319 }
320
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700321 @Override
322 public void onFieldChanged(String column, String value) {
Dmitri Plotnikov11bb73b2011-02-25 20:39:58 -0800323 if (!isFieldChanged(column, value)) {
Dmitri Plotnikov7d4a8dd2010-12-07 20:49:25 -0800324 return;
325 }
326
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700327 // Field changes are saved directly
Katherine Kuane55faef2011-12-06 17:58:43 -0800328 saveValue(column, value);
329
330 // Notify listener if applicable
331 notifyEditorListener();
332 }
333
334 protected void saveValue(String column, String value) {
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700335 mEntry.put(column, value);
Katherine Kuane55faef2011-12-06 17:58:43 -0800336 }
337
Brian Attwell8f9d84f2014-11-03 23:17:04 -0800338 /**
339 * Sub classes should call this at the end of {@link #setValues} once they finish changing
340 * isEmpty(). This is needed to fix b/18194655.
341 */
342 protected final void updateEmptiness() {
343 mWasEmpty = isEmpty();
344 }
345
Katherine Kuane55faef2011-12-06 17:58:43 -0800346 protected void notifyEditorListener() {
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700347 if (mListener != null) {
348 mListener.onRequest(EditorListener.FIELD_CHANGED);
349 }
Katherine Kuan25914362011-04-29 19:42:01 -0700350
351 boolean isEmpty = isEmpty();
352 if (mWasEmpty != isEmpty) {
353 if (isEmpty) {
Katherine Kuan37bddc22011-07-09 17:36:26 -0700354 if (mListener != null) {
355 mListener.onRequest(EditorListener.FIELD_TURNED_EMPTY);
356 }
Katherine Kuana94859f2011-07-20 13:38:27 -0700357 if (mIsDeletable) mDeleteContainer.setVisibility(View.INVISIBLE);
Katherine Kuan25914362011-04-29 19:42:01 -0700358 } else {
Katherine Kuan37bddc22011-07-09 17:36:26 -0700359 if (mListener != null) {
360 mListener.onRequest(EditorListener.FIELD_TURNED_NON_EMPTY);
361 }
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700362 if (mIsDeletable) mDeleteContainer.setVisibility(View.VISIBLE);
Katherine Kuan25914362011-04-29 19:42:01 -0700363 }
364 mWasEmpty = isEmpty;
Walter Jang23942c02015-09-25 10:53:41 -0700365
366 // Update the label text color
Walter Jang4f5594a2015-10-06 18:40:31 -0700367 if (mEditTypeAdapter != null) {
368 mEditTypeAdapter.notifyDataSetChanged();
369 }
Katherine Kuan25914362011-04-29 19:42:01 -0700370 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700371 }
372
Dmitri Plotnikov11bb73b2011-02-25 20:39:58 -0800373 protected boolean isFieldChanged(String column, String value) {
Daniel Lehmann60b239c2011-03-07 20:01:45 -0800374 final String dbValue = mEntry.getAsString(column);
375 // nullable fields (e.g. Middle Name) are usually represented as empty columns,
376 // so lets treat null and empty space equivalently here
377 final String dbValueNoNull = dbValue == null ? "" : dbValue;
378 final String valueNoNull = value == null ? "" : value;
379 return !TextUtils.equals(dbValueNoNull, valueNoNull);
Dmitri Plotnikov11bb73b2011-02-25 20:39:58 -0800380 }
381
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700382 protected void rebuildValues() {
383 setValues(mKind, mEntry, mState, mReadOnly, mViewIdGenerator);
384 }
385
386 /**
Brian Attwell8f9d84f2014-11-03 23:17:04 -0800387 * Prepare this editor using the given {@link DataKind} for defining structure and
388 * {@link ValuesDelta} describing the content to edit. When overriding this, be careful
389 * to call {@link #updateEmptiness} at the end.
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700390 */
391 @Override
Maurice Chu851222a2012-06-21 11:43:08 -0700392 public void setValues(DataKind kind, ValuesDelta entry, RawContactDelta state, boolean readOnly,
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700393 ViewIdGenerator vig) {
394 mKind = kind;
395 mEntry = entry;
396 mState = state;
397 mReadOnly = readOnly;
398 mViewIdGenerator = vig;
399 setId(vig.getId(state, kind, entry, ViewIdGenerator.NO_VIEW_INDEX));
400
401 if (!entry.isVisible()) {
402 // Hide ourselves entirely if deleted
403 setVisibility(View.GONE);
404 return;
405 }
406 setVisibility(View.VISIBLE);
407
408 // Display label selector if multiple types available
Walter Jangf7dfa542015-10-26 14:27:27 -0700409 final boolean hasTypes = RawContactModifier.hasEditTypes(kind);
410 setupLabelButton(hasTypes);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700411 mLabel.setEnabled(!readOnly && isEnabled());
Walter Jang155299e2016-08-25 10:38:52 -0700412 if (mKind.titleRes > 0) {
413 mLabel.setContentDescription(getContext().getResources().getString(mKind.titleRes));
414 }
Tingting Wangcfa628c2016-07-13 17:31:06 -0700415 mType = RawContactModifier.getCurrentType(entry, kind);
416 rebuildLabel();
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700417 }
418
419 public ValuesDelta getValues() {
420 return mEntry;
421 }
422
423 /**
424 * Prepare dialog for entering a custom label. The input value is trimmed: white spaces before
425 * and after the input text is removed.
426 * <p>
427 * If the final value is empty, this change request is ignored;
428 * no empty text is allowed in any custom label.
429 */
430 private Dialog createCustomDialog() {
Brian Attwellf1402272014-12-16 16:00:08 -0800431 final AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -0700432 final LayoutInflater layoutInflater = LayoutInflater.from(builder.getContext());
Adam Powelleb765ac2011-11-22 12:28:17 -0800433 builder.setTitle(R.string.customLabelPickerTitle);
434
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -0700435 final View view = layoutInflater.inflate(R.layout.contact_editor_label_name_dialog, null);
436 final EditText editText = (EditText) view.findViewById(R.id.custom_dialog_content);
437 editText.setInputType(INPUT_TYPE_CUSTOM);
438 editText.setSaveEnabled(true);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700439
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -0700440 builder.setView(view);
441 editText.requestFocus();
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700442
443 builder.setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() {
444 @Override
445 public void onClick(DialogInterface dialog, int which) {
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -0700446 final String customText = editText.getText().toString().trim();
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700447 if (ContactsUtils.isGraphic(customText)) {
Dmitri Plotnikov14918c02010-12-07 17:20:27 -0800448 final List<EditType> allTypes =
Tingting Wang6062cec2015-12-10 14:54:59 -0800449 RawContactModifier.getValidTypes(mState, mKind, null, true, null, true);
Dmitri Plotnikov14918c02010-12-07 17:20:27 -0800450 mType = null;
451 for (EditType editType : allTypes) {
452 if (editType.customColumn != null) {
453 mType = editType;
454 break;
455 }
456 }
457 if (mType == null) return;
458
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700459 mEntry.put(mKind.typeColumn, mType.rawValue);
460 mEntry.put(mType.customColumn, customText);
461 rebuildLabel();
462 requestFocusForFirstEditField();
463 onLabelRebuilt();
464 }
465 }
466 });
467
468 builder.setNegativeButton(android.R.string.cancel, null);
469
Daniel Lehmanna04dc9c2012-04-14 16:09:48 -0700470 final AlertDialog dialog = builder.create();
471 dialog.setOnShowListener(new OnShowListener() {
472 @Override
473 public void onShow(DialogInterface dialogInterface) {
474 updateCustomDialogOkButtonState(dialog, editText);
475 }
476 });
477 editText.addTextChangedListener(new TextWatcher() {
478 @Override
479 public void onTextChanged(CharSequence s, int start, int before, int count) {
480 }
481
482 @Override
483 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
484 }
485
486 @Override
487 public void afterTextChanged(Editable s) {
488 updateCustomDialogOkButtonState(dialog, editText);
489 }
490 });
491 dialog.getWindow().setSoftInputMode(
492 WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
493
494 return dialog;
495 }
496
497 /* package */ void updateCustomDialogOkButtonState(AlertDialog dialog, EditText editText) {
498 final Button okButton = dialog.getButton(AlertDialog.BUTTON_POSITIVE);
499 okButton.setEnabled(!TextUtils.isEmpty(editText.getText().toString().trim()));
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700500 }
501
502 /**
503 * Called after the label has changed (either chosen from the list or entered in the Dialog)
504 */
505 protected void onLabelRebuilt() {
506 }
507
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800508 protected void onTypeSelectionChange(int position) {
509 EditType selected = mEditTypeAdapter.getItem(position);
510 // See if the selection has in fact changed
511 if (mEditTypeAdapter.hasCustomSelection() && selected == CUSTOM_SELECTION) {
512 return;
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700513 }
514
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800515 if (mType == selected && mType.customColumn == null) {
516 return;
517 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700518
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800519 if (selected.customColumn != null) {
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800520 showDialog(DIALOG_ID_CUSTOM);
521 } else {
522 // User picked type, and we're sure it's ok to actually write the entry.
523 mType = selected;
524 mEntry.put(mKind.typeColumn, mType.rawValue);
Gary Mai186d7912016-08-10 14:41:45 -0700525 mSelectedLabelIndex = position;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800526 rebuildLabel();
527 requestFocusForFirstEditField();
528 onLabelRebuilt();
529 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700530 }
531
532 /* package */
533 void showDialog(int bundleDialogId) {
534 Bundle bundle = new Bundle();
535 bundle.putInt(DIALOG_ID_KEY, bundleDialogId);
536 getDialogManager().showDialogInView(this, bundle);
537 }
538
539 private DialogManager getDialogManager() {
540 if (mDialogManager == null) {
541 Context context = getContext();
542 if (!(context instanceof DialogManager.DialogShowingViewActivity)) {
543 throw new IllegalStateException(
544 "View must be hosted in an Activity that implements " +
545 "DialogManager.DialogShowingViewActivity");
546 }
547 mDialogManager = ((DialogManager.DialogShowingViewActivity)context).getDialogManager();
548 }
549 return mDialogManager;
550 }
551
552 @Override
553 public Dialog createDialog(Bundle bundle) {
554 if (bundle == null) throw new IllegalArgumentException("bundle must not be null");
555 int dialogId = bundle.getInt(DIALOG_ID_KEY);
556 switch (dialogId) {
557 case DIALOG_ID_CUSTOM:
558 return createCustomDialog();
559 default:
560 throw new IllegalArgumentException("Invalid dialogId: " + dialogId);
561 }
562 }
563
564 protected abstract void requestFocusForFirstEditField();
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800565
566 private class EditTypeAdapter extends ArrayAdapter<EditType> {
567 private final LayoutInflater mInflater;
568 private boolean mHasCustomSelection;
Brian Attwelld690dff2014-12-02 15:04:56 -0800569 private int mTextColorHintUnfocused;
570 private int mTextColorDark;
Gary Mai186d7912016-08-10 14:41:45 -0700571 private int mSelectedIndex;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800572
573 public EditTypeAdapter(Context context) {
574 super(context, 0);
575 mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
Brian Attwelld690dff2014-12-02 15:04:56 -0800576 mTextColorHintUnfocused = context.getResources().getColor(
577 R.color.editor_disabled_text_color);
Brian Attwelld690dff2014-12-02 15:04:56 -0800578 mTextColorDark = context.getResources().getColor(R.color.primary_text_color);
579
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800580
Dmitri Plotnikov4fa9cd02010-12-10 17:40:38 -0800581 if (mType != null && mType.customColumn != null) {
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800582
583 // Use custom label string when present
584 final String customText = mEntry.getAsString(mType.customColumn);
585 if (customText != null) {
586 add(CUSTOM_SELECTION);
587 mHasCustomSelection = true;
588 }
589 }
590
Tingting Wang6062cec2015-12-10 14:54:59 -0800591 addAll(RawContactModifier.getValidTypes(mState, mKind, mType, true, null, false));
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800592 }
593
594 public boolean hasCustomSelection() {
595 return mHasCustomSelection;
596 }
597
598 @Override
599 public View getView(int position, View convertView, ViewGroup parent) {
Brian Attwelld690dff2014-12-02 15:04:56 -0800600 final TextView view = createViewFromResource(
Brian Attwell043fba62014-10-30 11:11:56 -0700601 position, convertView, parent, R.layout.edit_simple_spinner_item);
602 // We don't want any background on this view. The background would obscure
603 // the spinner's background.
604 view.setBackground(null);
Brian Attwelld690dff2014-12-02 15:04:56 -0800605 // The text color should be a very light hint color when unfocused and empty. When
606 // focused and empty, use a less light hint color. When non-empty, use a dark non-hint
607 // color.
608 if (!LabeledEditorView.this.isEmpty()) {
609 view.setTextColor(mTextColorDark);
Brian Attwelld690dff2014-12-02 15:04:56 -0800610 } else {
611 view.setTextColor(mTextColorHintUnfocused);
612 }
Brian Attwell043fba62014-10-30 11:11:56 -0700613 return view;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800614 }
615
616 @Override
617 public View getDropDownView(int position, View convertView, ViewGroup parent) {
Gary Mai186d7912016-08-10 14:41:45 -0700618 final CheckedTextView dropDownView = (CheckedTextView) createViewFromResource(
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800619 position, convertView, parent, android.R.layout.simple_spinner_dropdown_item);
Gary Mai186d7912016-08-10 14:41:45 -0700620 dropDownView.setBackground(getContext().getDrawable(R.drawable.drawer_item_background));
621 dropDownView.setChecked(position == mSelectedIndex);
622 return dropDownView;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800623 }
624
Brian Attwelld690dff2014-12-02 15:04:56 -0800625 private TextView createViewFromResource(int position, View convertView, ViewGroup parent,
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800626 int resource) {
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800627 TextView textView;
628
629 if (convertView == null) {
Katherine Kuanfd709032011-08-02 14:18:16 -0700630 textView = (TextView) mInflater.inflate(resource, parent, false);
Brian Attwell043fba62014-10-30 11:11:56 -0700631 textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, getResources().getDimension(
632 R.dimen.editor_form_text_size));
Brian Attwelld690dff2014-12-02 15:04:56 -0800633 textView.setTextColor(mTextColorDark);
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800634 } else {
Katherine Kuanfd709032011-08-02 14:18:16 -0700635 textView = (TextView) convertView;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800636 }
637
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800638 EditType type = getItem(position);
639 String text;
640 if (type == CUSTOM_SELECTION) {
641 text = mEntry.getAsString(mType.customColumn);
642 } else {
643 text = getContext().getString(type.labelRes);
644 }
Katherine Kuanfd709032011-08-02 14:18:16 -0700645 textView.setText(text);
646 return textView;
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800647 }
Gary Mai186d7912016-08-10 14:41:45 -0700648
649 public void setSelectedIndex(int selectedIndex) {
650 mSelectedIndex = selectedIndex;
651 }
Dmitri Plotnikov2fcfa492010-12-07 16:10:33 -0800652 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700653}