blob: c48e3c9c5368c0a17b40f8935b762a7b0c7952e6 [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
19import com.android.contacts.ContactsUtils;
20import com.android.contacts.R;
Dmitri Plotnikov4597c922010-11-15 13:03:06 -080021import com.android.contacts.model.AccountType.EditField;
Daisuke Miyakawa405671a2011-03-29 09:34:07 -070022import com.android.contacts.model.DataKind;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070023import com.android.contacts.model.EntityDelta;
24import com.android.contacts.model.EntityDelta.ValuesDelta;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070025import com.android.contacts.util.NameConverter;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070026
27import android.content.Context;
28import android.content.Entity;
29import android.graphics.Rect;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070030import android.graphics.Typeface;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070031import android.os.Parcel;
32import android.os.Parcelable;
Katherine Kuana2d4a962011-05-06 15:20:21 -070033import android.provider.ContactsContract.CommonDataKinds.StructuredName;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070034import android.telephony.PhoneNumberFormattingTextWatcher;
35import android.text.Editable;
36import android.text.InputType;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070037import android.text.Spannable;
Katherine Kuan1b220732011-04-28 18:45:39 -070038import android.text.TextUtils;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070039import android.text.TextWatcher;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070040import android.text.style.StyleSpan;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070041import android.util.AttributeSet;
42import android.view.Gravity;
43import android.view.View;
Katherine Kuan63ffb902011-04-26 19:53:40 -070044import android.view.ViewGroup;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070045import android.widget.EditText;
Katherine Kuan12a93632011-05-05 13:38:01 -070046import android.widget.ImageView;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070047import android.widget.LinearLayout;
Dave Santoroda5bf1c2011-05-03 10:30:34 -070048
49import java.util.Map;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070050
51/**
Katherine Kuan63ffb902011-04-26 19:53:40 -070052 * Simple editor that handles labels and any {@link EditField} defined for the
53 * entry. Uses {@link ValuesDelta} to read any existing {@link Entity} values,
54 * and to correctly write any changes values.
Daniel Lehmann392ccec2010-11-01 20:50:03 -070055 */
56public class TextFieldsEditorView extends LabeledEditorView {
57 private EditText[] mFieldEditTexts = null;
Katherine Kuan63ffb902011-04-26 19:53:40 -070058 private ViewGroup mFields = null;
Katherine Kuan12a93632011-05-05 13:38:01 -070059 private View mExpansionViewContainer;
60 private ImageView mExpansionView;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070061 private boolean mHideOptional = true;
62 private boolean mHasShortAndLongForms;
Katherine Kuan1ebbd5b2011-08-05 14:39:34 -070063 private int mMinFieldHeight;
Daniel Lehmann392ccec2010-11-01 20:50:03 -070064
65 public TextFieldsEditorView(Context context) {
66 super(context);
67 }
68
69 public TextFieldsEditorView(Context context, AttributeSet attrs) {
70 super(context, attrs);
71 }
72
73 public TextFieldsEditorView(Context context, AttributeSet attrs, int defStyle) {
74 super(context, attrs, defStyle);
75 }
76
Katherine Kuan63ffb902011-04-26 19:53:40 -070077 /** {@inheritDoc} */
78 @Override
79 protected void onFinishInflate() {
80 super.onFinishInflate();
81
82 setDrawingCacheEnabled(true);
83 setAlwaysDrawnWithCacheEnabled(true);
84
Katherine Kuan1ebbd5b2011-08-05 14:39:34 -070085 mMinFieldHeight = mContext.getResources().getDimensionPixelSize(
86 R.dimen.editor_min_line_item_height);
Katherine Kuan63ffb902011-04-26 19:53:40 -070087 mFields = (ViewGroup) findViewById(R.id.editors);
Katherine Kuan12a93632011-05-05 13:38:01 -070088 mExpansionView = (ImageView) findViewById(R.id.expansion_view);
89 mExpansionViewContainer = findViewById(R.id.expansion_view_container);
90 mExpansionViewContainer.setOnClickListener(new OnClickListener() {
Katherine Kuan63ffb902011-04-26 19:53:40 -070091 @Override
92 public void onClick(View v) {
93 // Save focus
94 final View focusedChild = getFocusedChild();
95 final int focusedViewId = focusedChild == null ? -1 : focusedChild.getId();
96
97 // Reconfigure GUI
98 mHideOptional = !mHideOptional;
99 onOptionalFieldVisibilityChange();
100 rebuildValues();
101
102 // Restore focus
103 View newFocusView = findViewById(focusedViewId);
104 if (newFocusView == null || newFocusView.getVisibility() == GONE) {
105 // find first visible child
106 newFocusView = TextFieldsEditorView.this;
107 }
108 newFocusView.requestFocus();
109 }
110 });
111 }
112
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700113 @Override
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700114 public void setEnabled(boolean enabled) {
115 super.setEnabled(enabled);
116
117 if (mFieldEditTexts != null) {
118 for (int index = 0; index < mFieldEditTexts.length; index++) {
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800119 mFieldEditTexts[index].setEnabled(!isReadOnly() && enabled);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700120 }
121 }
Katherine Kuan12a93632011-05-05 13:38:01 -0700122 mExpansionView.setEnabled(!isReadOnly() && enabled);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700123 }
124
125 /**
126 * Creates or removes the type/label button. Doesn't do anything if already correctly configured
127 */
Katherine Kuan12a93632011-05-05 13:38:01 -0700128 private void setupExpansionView(boolean shouldExist, boolean collapsed) {
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700129 if (shouldExist) {
Katherine Kuan12a93632011-05-05 13:38:01 -0700130 mExpansionViewContainer.setVisibility(View.VISIBLE);
131 mExpansionView.setImageResource(collapsed
Daniel Lehmann915ffc52010-11-10 11:33:44 -0800132 ? R.drawable.ic_menu_expander_minimized_holo_light
133 : R.drawable.ic_menu_expander_maximized_holo_light);
Katherine Kuan63ffb902011-04-26 19:53:40 -0700134 } else {
Katherine Kuan12a93632011-05-05 13:38:01 -0700135 mExpansionViewContainer.setVisibility(View.GONE);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700136 }
137 }
138
139 @Override
140 protected void requestFocusForFirstEditField() {
141 if (mFieldEditTexts != null && mFieldEditTexts.length != 0) {
142 EditText firstField = null;
143 boolean anyFieldHasFocus = false;
144 for (EditText editText : mFieldEditTexts) {
145 if (firstField == null && editText.getVisibility() == View.VISIBLE) {
146 firstField = editText;
147 }
148 if (editText.hasFocus()) {
149 anyFieldHasFocus = true;
150 break;
151 }
152 }
153 if (!anyFieldHasFocus && firstField != null) {
154 firstField.requestFocus();
155 }
156 }
157 }
158
159 @Override
160 public void setValues(DataKind kind, ValuesDelta entry, EntityDelta state, boolean readOnly,
161 ViewIdGenerator vig) {
162 super.setValues(kind, entry, state, readOnly, vig);
163 // Remove edit texts that we currently have
164 if (mFieldEditTexts != null) {
165 for (EditText fieldEditText : mFieldEditTexts) {
Katherine Kuan63ffb902011-04-26 19:53:40 -0700166 mFields.removeView(fieldEditText);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700167 }
168 }
169 boolean hidePossible = false;
170
171 int fieldCount = kind.fieldList.size();
172 mFieldEditTexts = new EditText[fieldCount];
173 for (int index = 0; index < fieldCount; index++) {
174 final EditField field = kind.fieldList.get(index);
175 final EditText fieldView = new EditText(mContext);
176 fieldView.setLayoutParams(new LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT,
Katherine Kuan1ebbd5b2011-08-05 14:39:34 -0700177 field.isMultiLine() ? LayoutParams.WRAP_CONTENT : mMinFieldHeight));
Katherine Kuana2d4a962011-05-06 15:20:21 -0700178 fieldView.setTextAppearance(getContext(), kind.textAppearanceResourceId);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700179 fieldView.setGravity(Gravity.TOP);
180 mFieldEditTexts[index] = fieldView;
181 fieldView.setId(vig.getId(state, kind, entry, index));
182 if (field.titleRes > 0) {
183 fieldView.setHint(field.titleRes);
184 }
185 int inputType = field.inputType;
186 fieldView.setInputType(inputType);
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700187 if (field.isFullName) {
188 fieldView.addTextChangedListener(new NameFormattingTextWatcher());
189 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700190 if (inputType == InputType.TYPE_CLASS_PHONE) {
191 fieldView.addTextChangedListener(new PhoneNumberFormattingTextWatcher(
192 ContactsUtils.getCurrentCountryIso(mContext)));
193 }
194 fieldView.setMinLines(field.minLines);
195
196 // Read current value from state
197 final String column = field.column;
198 final String value = entry.getAsString(column);
199 fieldView.setText(value);
200
Katherine Kuan5e330ed2011-07-15 15:06:06 -0700201 // Show the delete button if we have a non-null value
202 setDeleteButtonVisible(value != null);
203
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700204 // Prepare listener for writing changes
205 fieldView.addTextChangedListener(new TextWatcher() {
206 @Override
207 public void afterTextChanged(Editable s) {
208 // Trigger event for newly changed value
209 onFieldChanged(column, s.toString());
210 }
211
212 @Override
213 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
214 }
215
216 @Override
217 public void onTextChanged(CharSequence s, int start, int before, int count) {
218 }
219 });
220
Dmitri Plotnikov02bb1252010-12-06 09:39:05 -0800221 fieldView.setEnabled(isEnabled() && !readOnly);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700222
223 if (field.shortForm) {
224 hidePossible = true;
225 mHasShortAndLongForms = true;
226 fieldView.setVisibility(mHideOptional ? View.VISIBLE : View.GONE);
227 } else if (field.longForm) {
228 hidePossible = true;
229 mHasShortAndLongForms = true;
230 fieldView.setVisibility(mHideOptional ? View.GONE : View.VISIBLE);
231 } else {
232 // Hide field when empty and optional value
233 final boolean couldHide = (!ContactsUtils.isGraphic(value) && field.optional);
234 final boolean willHide = (mHideOptional && couldHide);
235 fieldView.setVisibility(willHide ? View.GONE : View.VISIBLE);
236 hidePossible = hidePossible || couldHide;
237 }
238
Katherine Kuan63ffb902011-04-26 19:53:40 -0700239 mFields.addView(fieldView);
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700240 }
241
242 // When hiding fields, place expandable
Katherine Kuan12a93632011-05-05 13:38:01 -0700243 setupExpansionView(hidePossible, mHideOptional);
244 mExpansionView.setEnabled(!readOnly && isEnabled());
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700245 }
246
Katherine Kuan25914362011-04-29 19:42:01 -0700247 @Override
248 public boolean isEmpty() {
Katherine Kuan1b220732011-04-28 18:45:39 -0700249 for (int i = 0; i < mFields.getChildCount(); i++) {
250 EditText editText = (EditText) mFields.getChildAt(i);
Katherine Kuan25914362011-04-29 19:42:01 -0700251 if (!TextUtils.isEmpty(editText.getText())) {
252 return false;
Katherine Kuan1b220732011-04-28 18:45:39 -0700253 }
254 }
Katherine Kuan25914362011-04-29 19:42:01 -0700255 return true;
Katherine Kuan1b220732011-04-28 18:45:39 -0700256 }
257
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700258 /**
259 * Returns true if the editor is currently configured to show optional fields.
260 */
261 public boolean areOptionalFieldsVisible() {
262 return !mHideOptional;
263 }
264
265 public boolean hasShortAndLongForms() {
266 return mHasShortAndLongForms;
267 }
268
269 /**
270 * Populates the bound rectangle with the bounds of the last editor field inside this view.
271 */
272 public void acquireEditorBounds(Rect bounds) {
273 if (mFieldEditTexts != null) {
274 for (int i = mFieldEditTexts.length; --i >= 0;) {
275 EditText editText = mFieldEditTexts[i];
276 if (editText.getVisibility() == View.VISIBLE) {
277 bounds.set(editText.getLeft(), editText.getTop(), editText.getRight(),
278 editText.getBottom());
279 return;
280 }
281 }
282 }
283 }
284
285 /**
286 * Saves the visibility of the child EditTexts, and mHideOptional.
287 */
288 @Override
289 protected Parcelable onSaveInstanceState() {
290 Parcelable superState = super.onSaveInstanceState();
291 SavedState ss = new SavedState(superState);
292
293 ss.mHideOptional = mHideOptional;
294
Dmitri Plotnikov5ec40b02011-03-01 18:36:16 -0800295 final int numChildren = mFieldEditTexts == null ? 0 : mFieldEditTexts.length;
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700296 ss.mVisibilities = new int[numChildren];
297 for (int i = 0; i < numChildren; i++) {
298 ss.mVisibilities[i] = mFieldEditTexts[i].getVisibility();
299 }
300
301 return ss;
302 }
303
304 /**
305 * Restores the visibility of the child EditTexts, and mHideOptional.
306 */
307 @Override
308 protected void onRestoreInstanceState(Parcelable state) {
309 SavedState ss = (SavedState) state;
310 super.onRestoreInstanceState(ss.getSuperState());
311
312 mHideOptional = ss.mHideOptional;
313
314 int numChildren = Math.min(mFieldEditTexts.length, ss.mVisibilities.length);
315 for (int i = 0; i < numChildren; i++) {
316 mFieldEditTexts[i].setVisibility(ss.mVisibilities[i]);
317 }
318 }
319
320 private static class SavedState extends BaseSavedState {
321 public boolean mHideOptional;
322 public int[] mVisibilities;
323
324 SavedState(Parcelable superState) {
325 super(superState);
326 }
327
328 private SavedState(Parcel in) {
329 super(in);
330 mVisibilities = new int[in.readInt()];
331 in.readIntArray(mVisibilities);
332 }
333
334 @Override
335 public void writeToParcel(Parcel out, int flags) {
336 super.writeToParcel(out, flags);
337 out.writeInt(mVisibilities.length);
338 out.writeIntArray(mVisibilities);
339 }
340
Daniel Lehmanncfb525b2010-11-02 19:24:54 -0700341 @SuppressWarnings({"unused", "hiding" })
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700342 public static final Parcelable.Creator<SavedState> CREATOR
343 = new Parcelable.Creator<SavedState>() {
344 @Override
345 public SavedState createFromParcel(Parcel in) {
346 return new SavedState(in);
347 }
348
349 @Override
350 public SavedState[] newArray(int size) {
351 return new SavedState[size];
352 }
353 };
354 }
Dave Santoroda5bf1c2011-05-03 10:30:34 -0700355
356 private class NameFormattingTextWatcher implements TextWatcher {
357
358
359 @Override
360 public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
361
362 @Override
363 public void onTextChanged(CharSequence s, int start, int before, int count) {}
364
365 @Override
366 public void afterTextChanged(Editable s) {
367 String displayName = s.toString();
368 Map<String, String> structuredName = NameConverter.displayNameToStructuredName(
369 getContext(), displayName);
370 String givenName = structuredName.get(StructuredName.GIVEN_NAME);
371 if (!TextUtils.isEmpty(givenName)) {
372 int spanStart = -1;
373 int spanEnd = -1;
374 if (displayName.startsWith(givenName + " ")) {
375 spanStart = 0;
376 spanEnd = givenName.length();
377 } else {
378 spanStart = displayName.lastIndexOf(" " + givenName);
379 if (spanStart > -1) {
380 spanStart++;
381 spanEnd = spanStart + givenName.length();
382 }
383 }
384
385 // If the requested range is already bolded, don't make any changes.
386 if (spanStart > -1) {
387 StyleSpan[] existingSpans = s.getSpans(0, s.length(), StyleSpan.class);
388 for (StyleSpan span : existingSpans) {
389 if (span.getStyle() == Typeface.BOLD
390 && s.getSpanStart(span.getUnderlying()) == spanStart
391 && s.getSpanEnd(span.getUnderlying()) == spanEnd) {
392 // Nothing to do - the correct portion is already bolded.
393 return;
394 }
395 }
396
397 // Clear any existing bold style spans.
398 for (StyleSpan span : existingSpans) {
399 if (span.getStyle() == Typeface.BOLD) {
400 s.removeSpan(span);
401 }
402 }
403
404 // Set the new bold span.
405 s.setSpan(new StyleSpan(Typeface.BOLD), spanStart, spanEnd,
406 Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
407 }
408 }
409 }
410 }
Katherine Kuan2293e552011-07-21 20:25:44 -0700411
412 @Override
413 public void clearAllFields() {
414 if (mFieldEditTexts != null) {
415 for (EditText fieldEditText : mFieldEditTexts) {
416 // Update UI (which will trigger a state change through the {@link TextWatcher})
417 fieldEditText.setText("");
418 }
419 }
420 }
Daniel Lehmann392ccec2010-11-01 20:50:03 -0700421}